应该都不难看出这是个树形dp吧,那接下来就讲过程了。
状态设置
我们设置一个dp数组f[i][j],表示以i为根的树,当前状态为j时设置看守的最小花费。
a.i表示当前结点的编号。
b.j表示当前结点的状态,根据题意,只要在某一个结点设置看守,那么它的父亲与儿子也会受到影响。所以对于一个结点,可能会有以下三种情况:1.被它的父亲影响;2.被它自身设置的看守影响;3,被它的某一个儿子影响。
这样,我们就设好了状态。
状态转移方程
既然一个结点可能有3种状态,那么我们就对于每一种状态都推一下状态转移方程。
a.状态1(被它的父亲影响):
因为它的父亲已经设置了看守,那么它的儿子可能会被儿子结点自身看到(即f[j][2],j∈son[j]),也可能被它的儿子的儿子看到(即f[j][3],j∈son[j])。所以这个状态的转移方程就是f[i][1]=min(f[j][1],f[j][2])(j∈son[i])
b.状态2(它自身设置看守)
既然它自身已经设置了看守,那么它的子结点是什么状态就都可以了。
状态转移方程:f[i][2]=min(f[j][1],f[j][2],f[j][3])+r[i](j∈son[i])
c.状态3(被它的儿子影响)
首先,我们先对于它的儿子取与不去先dp一遍,同时记录一个变量d(后面会解释) f[i][3]=min(f[j][1],f[j][2]),d=min(f[j][2]-min(f[j][1],f[j][2]))(j∈son[i])
然后,我们再f[i][3]+=d,这里d的作用是强制选择一个花费最小的点,因为在之前的dp时,我们不能确定它的儿子结点有没有被选择。
一些需要注意的地方
没有,看代码吧
#include<cstdio>
#include<algorithm>
using namespace std;
int f[1510][3];
int n;
int sum[1510];
int head[1510],cnt;
struct edge{
int nxt,go;
}e[3010];
void add(int u,int v){e[++cnt]=(edge){head[u],v},head[u]=cnt;}
void dfs(int u,int fa)
{
int ex=0x7fffffff;
for(int i=head[u];i;i=e[i].nxt)
{
int v=e[i].go;
if(v==fa)
continue;
dfs(v,u);
f[u][0]+=min(f[v][1],f[v][2]);
f[u][1]+=min(f[v][0],min(f[v][1],f[v][2]));
f[u][2]+=min(f[v][1],f[v][2]);
ex=min(ex,-min(f[v][1],f[v][2])+f[v][1]);
}
f[u][1]+=sum[u];
f[u][2]+=ex;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int num,fum,son;
scanf("%d %d %d",&num,&fum,&son);
sum[num]=fum;
for(int j=1;j<=son;j++)
{
int v;
scanf("%d",&v);//注意连两条边
add(num,v);
add(v,num);
}
}
dfs(1,-1);
printf("%d\n",min(f[1][1],f[1][2]));
return 0;
}

京公网安备 11010502036488号