题目大意:
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1054
给出一棵树,树的每一条边左右两个点至少得有一个点被选择。求选择的最小数量。
思路:
很明显的树形DP。和没有上司的舞会很像。
对于每一个点,如果选择它,那么它的子节点就可以选择,也可以不选择,那么选择它的最优答案就是它的子节点选择或不选择的最优答案之和。
那么如果不选择这个点,那么它的所有子节点都必须选择,所以答案就是它的所有子节点被选择的最优答案之和。
设f[x][1]f[x][1]f[x][1]为选择点xxx的最优答案,f[x][0]f[x][0]f[x][0]为不选择点xxx的最优答案,那么就有(yyy为xxx的子节点):
f[x][1]=∑i=1m[x]min(f[y[i]][1],f[y[i]][0])f[x][1]=\sum^{m[x]}_{i=1}min(f[y[i]][1],f[y[i]][0])f[x][1]=i=1∑m[x]min(f[y[i]][1],f[y[i]][0])
f[x][0]=∑i=1m[x]f[y[i]][1]f[x][0]=\sum^{m[x]}_{i=1}f[y[i]][1]f[x][0]=i=1∑m[x]f[y[i]][1]
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#define N 20010
using namespace std;
int n,m,x,y,k,root,f[N][2],head[N],out[N];
char c;
bool Root[N];
struct edge
{
int to,next;
}e[N];
void add(int from,int to)
{
k++;
e[k].to=to;
e[k].next=head[from];
head[from]=k;
}
void dp(int x,int fa)
{
int ans1=0,ans2=0;
if (!out[x]) return;
for (int i=head[x];~i;i=e[i].next) //找这个点的所有子节点
{
int v=e[i].to;
if (v==fa) continue;
dp(v,x);
ans1+=min(f[v][1],f[v][0]);
ans2+=f[v][1];
}
f[x][1]=ans1+1;
f[x][0]=ans2;
}
int main()
{
while (cin>>n)
{
memset(Root,0,sizeof(Root));
memset(f,0x3f3f3f3f,sizeof(f));
memset(out,0,sizeof(out));
memset(head,-1,sizeof(head));
memset(e,0,sizeof(e));
k=0;
for (int i=1;i<=n;i++)
{
cin>>x>>c>>c>>m>>c; //这道题拥有者神奇的读入
x++;
for (int j=1;j<=m;j++)
{
scanf("%d",&y);
y++;
add(x,y);
add(y,x);
Root[y]=true; //点y不可能是根节点
out[x]++; //x的出度
}
}
for (int i=1;i<=n;i++)
{
if (!Root[i]) root=i; //是根节点
if (!out[i]) //是叶子节点
{
f[i][1]=1;
f[i][0]=0;
}
}
dp(root,-233);
printf("%d\n",min(f[root][0],f[root][1]));
}
return 0;
}