F[X][0]表示结点X的父亲放了守卫的最小花费,F[X][1]表示结点X自身放了守卫的最小花费,F[X][2]表示结点X和父亲都不放守卫,X的其中一个儿子放了守卫的最小花费。
这样,父亲放了守卫,X可以选择放守卫(F[X][1]),或者可以是儿子放自身不放守卫(F[son][1],F[son][2])。
自身放守卫,则儿子的状态都是父亲放了守卫(F[son][0])。
其中一个儿子放了守卫,则枚举哪个儿子是最优值。(F[son][1],F[son][0]),这样最终可以推出转移方程。
#include<cstdio>
#include<algorithm>
#include<iostream>
#define mem(a,b) memset(a,b,sizeof(a))
#define maxn 1502
#define MAX 1000000007
using namespace std;
typedef long long ll;
struct st{
int num,c;//num儿子数,c代价
int s[maxn];
}a[maxn];
int u[maxn];
int f[maxn][4];
void dp(int now)
{
if(u[now])
{
return ;
}
if(!a[now].num)
{
f[now][1]=f[now][2]=a[now].c;
f[now][0]=0;
}
for(int i=1;i<=a[now].num;i++)
{
dp(a[now].s[i]);//枚举子节点,搜索
}
f[now][0]=0;
for(int j=1;j<=a[now].num;j++)
{
f[now][0]+=min(f[a[now].s[j]][1],f[a[now].s[j]][2]);
}
f[now][2]=MAX;
for(int j=1;j<=a[now].num;j++)
{
f[now][2]=min(f[now][2],f[now][0]-min(f[a[now].s[j]][1],f[a[now].s[j]][2])+f[a[now].s[j]][1]);
}
f[now][1]=a[now].c;
for(int j=1;j<=a[now].num;j++)
{
f[now][1]+=f[a[now].s[j]][0];//加上所有子节点的代价
}
f[now][0]=min(f[now][0],f[now][1]);//父节点看守或自己看守
}
int main()
{
int n;
while(~scanf("%d",&n))
{
mem(f,MAX);
for(int i=1;i<=n;i++)
{
int x;
scanf("%d",&x);
scanf("%d%d",&a[x].c,&a[x].num);
for(int j=1;j<=a[x].num;j++)
{
scanf("%d",&a[x].s[j]);
u[a[x].s[j]]=1;
}
}
int m,ans;
for(m=1;m<=n;m++)if(!u[m])break;
mem(u,0);
dp(m);
ans=min(f[m][1],f[m][2]);//自己放或儿子放
printf("%d\n",ans);
}
return 0;
}
2020-03-17 一更
这道题就是互为父节点子节点中有一个有人看守即可。
所以一个点自己看守的代价就是所有子节点状态为父亲看守的加和。
f[now][1]+=f[a[now].s[j]],[0];
一个点父亲看守的代价就是min(这个父亲看守,这个点看守)。
f[now][0]=min(f[now][1],f[now][0]);
一个点儿子看守的代价就是min(这个点父亲看守的代价减去这个子节点自己看守或子节点的儿子看守之间的最小代价再加上这个子节点自己看守的代价,本身)。
好像很难理解,看下代码(注意此时的父节点代价不是最终的)
f[now][2]=min(f[now][0]-min(f[a[now].s[j]][1],f[a[now].s[j]][2])+f[a[now].s[j]][1],f[now][2]);
注意是先遍历儿子在遍历根,最后输出根,从下往上遍历树,根的下标是m。