以前學樹型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+背包,如果不復雜的化,也有點按部就班的感覺了~下次想出狀態,尋軌道距應該就能做出來了。