题目即求树上的点覆盖数。
对于一个图的点覆盖集,都不存在多项式内的解法,但如果是树的话,却挺容易,有三种方法,第一个是贪心,第二个是树形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;
}