三道简单树型dp+01背包~~hdu1561,poj1947,zoj3626
以前学树型dp就是随便的看了几道题,没有特别注意树型dp中的小分类的总结,直到上次浙大月赛一道很简单的树型dp都不会,才意识到自己太水了~~come on!
hdu1561:题目给出了很多棵有根树,如果把每棵树的根节点都与0相连,则就是一棵完整的有根树了(N<=200),ACboy从根节点出发,他最多可以攻占m个城市,而每个城市的财富值不一定相同,问ACboy最多可以获得多少财富。就是以每个跟节点做01背包就可以了,dp[i][j]表示以i为根节点已经攻占了j个城市获得的最大财富。不过要注意一下最后要加上根节点的值,因为要求攻占了根节点才能往下攻占,当然节点0除外,具体见代码和注释:
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 #include<vector> 7 #define see(x) cout<<#x<<":"<<x<<endl; 8 using namespace std; 9 const int maxn = 205; 10 int w[maxn], dp[maxn][maxn]; 11 vector<int> son[maxn]; 12 int n, m; 13 void dfs(int x){ 14 int i, j, k; 15 if(son[x].size()==0){ 16 dp[x][1] = w[x]; 17 return; 18 } 19 for(i=0;i<son[x].size();i++){ 20 dfs(son[x][i]); 21 } 22 for(i=0;i<son[x].size();i++){ 23 for(j=m;j>=0;j--){ 24 for(k=0;k<=j;k++){ 25 if(dp[son[x][i]][k]!=-1&&dp[x][j-k]!=-1){ 26 dp[x][j] = max(dp[x][j],dp[son[x][i]][k]+dp[x][j-k]); 27 } 28 } 29 } 30 } 31 /*上面就是基本树型DP加背包的基本解法了*/ 32 if(x!=0){ //x为0节点时,不需要这种要求 33 for(j=m;j>=0;j--){//处理一下必须攻占根节点才能攻占剩下的子节点的条件,逆序正好可以处理完 34 if(dp[x][j-1]!=-1){ 35 dp[x][j] = dp[x][j-1]+w[x]; 36 } 37 } 38 } 39 } 40 void Init(int n){ 41 memset(dp,-1,sizeof(dp)); 42 for(int i=0;i<=n;i++){ 43 dp[i][0] = 0; 44 } 45 for(int i=0;i<=n;i++){ 46 son[i].clear(); 47 } 48 } 49 int main(){ 50 int i, j, k, l; 51 while(~scanf("%d%d",&n,&m)&&(n||m)){ 52 Init(n); 53 for(i=1;i<=n;i++){ 54 scanf("%d%d",&k,&w[i]); 55 son[k].push_back(i); 56 } 57 w[0] = 0; dfs(0); 58 printf("%d\n",dp[0][m]); 59 } 60 }
poj1947:给出一棵有根树,问孤立出大小为P的子树要断开多少边。就是一个容量为p的背包,树型dp。dp[i][j]表示以i为根孤立出j个节点的树至少需要断开多少条边,于是dp[x][j] = min{dp[x1][j1]+dp[x2][j2]+dp[x3][j3]+……+dp[xn][jn]},j1+j2+j3+……+jn = j,但是也不是完全这样了,其实在对每个背包进行更新时,应该是dp[x][j] = min{dp[x][j],dp[son[x][i]][k]+dp[x][j-k]-2},减2是因为要减去算了两次的x到son[x][i]的那条边,具体见代码,写的挺纠结的。
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #include<vector> 7 using namespace std; 8 const int maxn = 155; 9 vector<int> son[maxn]; 10 bool vis[maxn]; 11 int dp[maxn][maxn]; 12 int n, p; 13 void dfs(int root, int x){ 14 int i, j, k; 15 if(x==root) dp[x][1] = son[x].size(); 16 else dp[x][1] = son[x].size()+1; 17 18 for(i=0;i<son[x].size();i++){ 19 dfs(root,son[x][i]); 20 } 21 for(i=0;i<son[x].size();i++){ 22 for(j=p;j>=0;j--){ 23 for(k=0;k<=j;k++){ 24 if(dp[x][k]<maxn&&dp[son[x][i]][j-k]<maxn){ 25 dp[x][j] = min(dp[x][j],dp[x][k]+dp[son[x][i]][j-k]-2); 26 } 27 } 28 } 29 } 30 } 31 void Init(int n){ 32 int i, j; 33 for(i=0;i<=n;i++){ 34 for(j=0;j<=n;j++){ 35 dp[i][j] = maxn; 36 } 37 son[i].clear(); 38 vis[i] = 0; 39 } 40 } 41 int main(){ 42 int i, j, k, x1, x2, ans; 43 while(~scanf("%d%d",&n,&p)){ 44 Init(n); 45 for(i=0;i<n-1;i++){ 46 scanf("%d%d",&x1,&x2); 47 son[x1].push_back(x2); 48 vis[x2] = 1; 49 } 50 for(i=1;i<=n;i++){ 51 if(!vis[i]){ 52 dfs(i,i);break; 53 } 54 } 55 ans = maxn; 56 for(i=1;i<=n;i++){ 57 ans = min(ans,dp[i][p]); 58 } 59 printf("%d\n",ans); 60 } 61 }
zoj3626:这个就是上次浙大月赛的题了,题意跟hdu1561类似,给出一棵树,n-1条边有不同的边全,n个城市有不同的财富,一个人最多只可以走m/2长度,为最多可以占领多少财富。dp[i][j]表示以i为根继续走了j个长度可以获得的最大财富,自己就是在刷完hdu1561之后简单的改改顺利的a了,两道题可能可以改成一样的写法,不过想到怎么写,就怎么写了
1 #include<iostream> 2 #include<cstdio> 3 #include<cmath> 4 #include<cstring> 5 #include<algorithm> 6 #include<vector> 7 #define see(x) cout<<#x<<":"<<x<<endl; 8 using namespace std; 9 const int maxn = 110; 10 vector<int> son[maxn], w[maxn]; 11 int val[maxn]; 12 int dp[maxn][205]; 13 int n, m; 14 void dfs(int x, int pre){ 15 int i, j, k; 16 if(son[x].size()==1&&pre==son[x][0]){ 17 return; 18 } 19 for(i=0;i<son[x].size();i++){ 20 if(pre!=son[x][i]){ 21 dfs(son[x][i],x); 22 } 23 } 24 for(i=0;i<son[x].size();i++){ 25 if(pre!=son[x][i]){ 26 for(j=m-w[x][i];j>=0;j--){ 27 for(k=0;k<=j;k++){ 28 if(dp[son[x][i]][k]!=-1&&dp[x][j-k]!=-1){ 29 dp[x][j+w[x][i]] = max(dp[x][j+w[x][i]],dp[son[x][i]][k]+dp[x][j-k]); 30 } 31 } 32 } 33 } 34 } 35 } 36 void Init(int n){ 37 memset(dp,-1,sizeof(dp)); 38 for(int i=0;i<=n;i++){ 39 son[i].clear(); 40 w[i].clear(); 41 } 42 } 43 int main(){ 44 int i, j, k, x1, x2, l, ans; 45 while(~scanf("%d",&n)){ 46 Init(n); 47 for(i=1;i<=n;i++){ 48 scanf("%d",&val[i]); 49 dp[i][0] = val[i]; 50 } 51 for(i=0;i<n-1;i++){ 52 scanf("%d%d%d",&x1,&x2,&l); 53 son[x1].push_back(x2);w[x1].push_back(l); 54 son[x2].push_back(x1);w[x2].push_back(l); 55 } 56 scanf("%d%d",&k,&m); 57 m/=2;dfs(k,-1); 58 ans = val[k]; 59 for(i=0;i<=m;i++){ 60 ans = max(ans,dp[k][i]); 61 } 62 printf("%d\n",ans); 63 } 64 }
按理说dp都没啥模板,但是其实树上dp+背包,如果不复杂的化,也有点按部就班的感觉了~下次想出状态,寻轨道距应该就能做出来了。