[SDOI2006] 保安站岗
题目链接:https://www.luogu.org/problemnew/show/P2458
看到题目之后——果断树形DP啊!!
但是怎么树形DP啊qwq。。。。开始想的是\(dp[i][0/1]\)来表示i节点选不选择。
那么状态转移方程:
\(dp[i][0]=\sum_{v=son(i)}^{}dp[v][1]\)
\(dp[i][1]=val[i]+\sum_{v=son(i)}min(dp[v][0],dp[v][1])\)
然后如果所有小的都是dp[v][0],找一个最小的dp[v][1]加上,然后减去原先对应加上的。
但是!!这样的写法不对!!
会被这样一组简单的数据hack掉:
9
1 1 1 2
2 5 3 3 4 5
3 100 1 6
4 100 1 7
5 100 2 8 9
6 1 0
7 1 0
8 1 0
9 1 0
为什么呢?是因为我们没有考虑用父亲节点控制该节点的情况。
所以正确的状态设计应该是这样的:
\(dp[i][0]\)表示节点\(i\)至少被父亲节点控制。
\(dp[i][1]\)表示节点\(i\)至少被子节点控制。
\(dp[i][2]\)表示节点\(i\)至少被自己控制。
那么正确的状态转移方程是:
\(dp[i][0]=\sum min(dp[son(i)][1],dp[son(i)][2])\)
\(dp[i][1]=\sum min(dp[son(i)][1],dp[son(i)][2])+dp[son(i)][2]\)
(其中至少有一个是1)
\(dp[i][2]=\sum min(dp[son(i)][0],dp[son(i)][1],dp[son(i)][2])+val[i]\)
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define MAXN 2010
using namespace std;
struct Edge{int nxt,to;}edge[MAXN<<1];
int edge_number,n,ans=2147483647;
int val[MAXN],head[MAXN];
long long dp[MAXN][3];
void add(int from,int to)
{
edge[++edge_number].nxt=head[from];
edge[edge_number].to=to;
head[from]=edge_number;
}
inline void search(int now,int fa)
{
long long sum=0;
dp[now][2]=val[now];
for(int i=head[now];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==fa) continue;
search(v,now);
dp[now][2]+=min(dp[v][2],min(dp[v][1],dp[v][0]));
sum+=min(dp[v][1],dp[v][2]);
}
dp[now][0]=sum;
dp[now][1]=2147483647;
for(int i=head[now];i;i=edge[i].nxt)
{
int v=edge[i].to;
if(v==fa) continue;
dp[now][1]=min(dp[now][1],sum+dp[v][2]-min(dp[v][1],dp[v][2]));
}
return;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int u,w,sum;
scanf("%d%d%d",&u,&w,&sum);
val[u]=w;
for(int j=1;j<=sum;j++)
{
int v;
scanf("%d",&v);
add(u,v);
add(v,u);
}
}
memset(dp,0x3f,sizeof(dp));
search(1,0);
printf("%lld\n",min(dp[1][1],dp[1][2]));
return 0;
}