题目即求树上的点覆盖数。
对于一个图的点覆盖集,都不存在多项式内的解法,但如果是树的话,却挺容易,有三种方法,第一个是贪心,第二个是树形dp,第三个是二分图匹配(树是一个二分图),我是用贪心做的,先深度优先遍历一遍得到遍历序列,将遍历序列反向(即后序遍历序列)进行贪心,这样可以保证对于每个节点来说,当其子树都被处理过后才轮到该节点,保证了贪心的正确性,贪心思路为:若当前点和当前点的父节点都不属于点覆盖集,则将当前节点的父节点加入到点覆盖集,并标记当前节点和其父节点都被覆盖。
#include <stdio.h> #include <vector> using namespace std; #define maxn 1600 vector <int> g[maxn]; int now,n,m,p[maxn],newpos[maxn],vis[maxn]; void dfs(int u) { newpos[now++]=u; int i,t; for(i=0;i<g[u].size();i++) { t=g[u][i]; if(vis[t]) continue; vis[t]=1; p[t]=u; dfs(t); } } int greedy() { bool s[maxn]={0}; bool set[maxn]={0}; int ans=0; int i; for(i=n-1;i>=1;i--) { int t=newpos[i]; if(!s[t]&&!s[p[t]]) { set[p[t]]=true; ans++; s[t]=true; s[p[t]]=true; } } return ans; } int main() { int i,j; int x,y; while(scanf("%d",&n)!=EOF) { memset(p,0,sizeof(p)); memset(g,0,sizeof(g)); memset(vis,0,sizeof(vis)); now=0; vis[0]=1; for(i=1;i<=n;i++) { scanf("%d:(%d)",&x,&m); for(j=0;j<m;j++) { scanf("%d",&y); g[x].push_back(y); g[y].push_back(x); } } dfs(0); printf("%d\n",greedy()); } return 0; }
再粘一份匹配的代码,来自http://gzhu-101majia.iteye.com/blog/1157335
#include <iostream> #include <stdio.h> #include <memory.h> #include <vector> using namespace std; const int N = 1505; int pre[N]; bool flag[N]; vector<int> map[N]; int n; int find(int cur) { int i, k; for(i = 0; i < map[cur].size(); i++) { k = map[cur][i]; if(!flag[k]) { flag[k] = true; if(pre[k] == -1 || find(pre[k])) { pre[k] = cur; return 1; } } } return 0; } int main() { int i, j, r, k, num, sum; while(scanf("%d", &n) != EOF) { memset(pre, -1, sizeof(pre)); for(i = 0; i < n; i++) map[i].clear(); for(i = 0; i < n; i++) { scanf("%d:(%d)", &k, &num); for(j = 0; j < num; j++) { scanf("%d", &r); map[k].push_back(r); //用邻接表 map[r].push_back(k); //建双向图 } } sum = 0; for(i = 0; i < n; i++) { memset(flag, false, sizeof(flag)); sum += find(i); } printf("%d\n", sum/2); } return 0; }