http://poj.org/problem?id=1463
有两种做法,一种是二分图,一种是树形DP,这里两种都做了。
二分图的。
就是经典的二分图最小点覆盖,要选取最小的点,使得所有的边与这些点关联,(可以一条边关联两个点,为了需要哟)
那么我们建的是无向边,因为可以互相关联,还是利用经典的拆点思想,
本来要若原图中i与j有边。我们要连的是i到j+n,j到i+n,但这样连的效果和i与j连,j与i连是一样的。所以为了节省空间,我们这样连
然后就是匈牙利算法,求最小点覆盖=最大匹配数
View Code
#include<iostream> #include<string.h> #include<stdio.h> #include<algorithm> #include<vector> #define maxn 2000 using namespace std; vector<int>node[maxn]; int mm[maxn]; int visit[maxn]; int n; int dfs(int fa) { for(int i=0;i<node[fa].size();i++) { int v=node[fa][i]; if(!visit[v]) { visit[v]=1; if(mm[v]==-1||dfs(mm[v])) { mm[v]=fa; return 1; } } } return 0; } void solve() { int cnt=0; memset(mm,-1,sizeof(mm)); for(int i=0;i<n;i++) { memset(visit,0,sizeof(visit)); if(dfs(i)) cnt++; } cout<<cnt/2<<endl; //因为连的是双向边,所以求的的匹配数是所需答案的2倍 } int main() { int u,num; int v; while(cin>>n) { for(int i=0;i<=n;i++) node[i].clear(); for(int i=0;i<n;i++) { scanf("%d:(%d)",&u,&num);//这个读入要注意 while(num--) { cin>>v; node[u].push_back(v);//要连双向边 node[v].push_back(u); } } solve(); } return 0; }
树形DP
用DP[I][0]来表示该点没有放兵,以这个点为根的子树所需的最少兵数。
用DP[I][1]来表示该点有放兵,以这个点为根的子树所需的最少兵数。
那么可以得到状态方程
dp[fa][0]+=dp[son][1];//如果该父亲不放,那么儿子必须放
dp[fa][1]+=min(dp[son][0],dp[son][1]);//如果该父亲放,儿子在放和不放之间选择最小的
访问的时候,因为要先知道儿子的信息,所以类似于后续遍历
View Code
#include<iostream> #include<string.h> #include<stdio.h> #include<algorithm> #include<vector> #define maxn 100000 using namespace std; int dp[maxn][2]; int n; int root; vector<int>node[maxn]; void init() { int u,v,num; root=-1; for(int i=0;i<=n;i++) node[i].clear(); for(int i=0;i<n;i++) { scanf("%d:(%d)",&u,&num);//读入要注意 if(root==-1)root=u;//第一个点是根 while(num--) { cin>>v; node[u].push_back(v);//连有向边 } } } int solve(int fa) { dp[fa][0]=0;dp[fa][1]=1; for(int i=0;i<node[fa].size();i++) { int son=node[fa][i]; solve(son); //其实觉得类似于后续遍历,因为父亲要用到儿子的信息, //所以要先知道儿子的 dp[fa][0]+=dp[son][1];//如果该父亲不放,那么儿子必须放 dp[fa][1]+=min(dp[son][0],dp[son][1]); //如果该父亲放,儿子在放和不放之间选择最小的 } return min(dp[fa][0],dp[fa][1]); } int main() { while(cin>>n) { init(); printf("%d\n",solve(root));// } return 0; }