题意:给定一棵树,选一个点集,使得所有边都与点集中的点相关联,求最小的点集。即最小点覆盖。
思路:一开始自己想到贪心,写出来也AC了,然后认识了树形dp。1、贪心:考虑叶子节点,如果选叶子节点,那么变换成不选叶子节点而选其父节点至少就能够覆盖这条边,而且还能覆盖更多的边。所以叶子节点是必然不选的,而其临接点是必然要选的。如此这般,用一个数组记录各点的度,用一个队列记录叶子节点,然后贪心选点。2、树形dp。一个点i选或不选只有两种可能,分别用dp[i][0]和dp[i][1]表示。dp[i][0]的话,其所有儿子必须选;dp[i][1]的话,其所有儿子可选可不选。用这个思路做dp即可。
1、贪心:
#include <stdio.h> #include <string.h> #define N 1505 struct edge{ int x,y,next; }e[N<<1]; int n,d[N],first[N],q[N],top; void add(int x,int y){ e[top].y = y; e[top].next = first[x]; first[x] = top++; } int main(){ freopen("a.txt","r",stdin); while(scanf("%d",&n)!=EOF){ int i,j,k,res=0,num,now,front,rear; top = 0; rear = front = -1; memset(first,-1,sizeof(first)); memset(d,0,sizeof(d)); for(k = 0;k<n;k++){ scanf("%d:(%d)",&i,&num); while(num--){ scanf("%d",&j); add(i,j);add(j,i); d[i]++;//记录端点的度数 d[j]++; } } for(i = 0;i<n;i++)//将叶节点进队 if(d[i] == 1) q[++rear] = i; while(front < rear){ i = q[++front]; if(!d[i])//如果与叶节点相连的边已经覆盖 continue; for(j=first[i];j!=-1;j=e[j].next)//找叶节点的父节点 if(d[e[j].y]){ now = e[j].y; break; } d[now] = 0;//选定这个父节点 res++; for(j=first[now];j!=-1;j=e[j].next){ if(d[e[j].y] > 0)//注意:没有删边,所以对于端点度已经为0的就不再减了,因为前面有判断度是否为0的地方 d[e[j].y]--; if(d[e[j].y] == 1) q[++rear] = e[j].y; } } printf("%d\n",res); } return 0; }
2、树形dp:
#include <stdio.h> #include <string.h> #define min(a,b) ((a)<(b)?(a):(b)) #define N 1505 struct edge{ int x,y,next; }e[N<<1]; int n,dp[N][2],first[N],top; void add(int x,int y){ e[top].y = y; e[top].next = first[x]; first[x] = top++; } int dfs(int x,int f){//f为x节点的父亲 int i,y; dp[x][1] = 1;//x点选择,则其至少为1 for(i=first[x];i!=-1;i=e[i].next){ y = e[i].y; if(y!=f){ dfs(y,x); dp[x][0] += dp[y][1]; dp[x][1] += min(dp[y][0],dp[y][1]);//一定注意儿子是可选可不选,而不是一定不选 } } } int main(){ freopen("a.txt","r",stdin); while(scanf("%d",&n)!=EOF){ int i,j,k,num; top = 0; memset(first,-1,sizeof(first)); memset(dp,0,sizeof(dp)); for(k = 0;k<n;k++){ scanf("%d:(%d)",&i,&num); while(num--){ scanf("%d",&j); add(i,j);add(j,i); } } dfs(0,0); printf("%d\n",min(dp[0][0],dp[0][1])); } return 0; }