1、poj 115 TELE
题意:一个树型网络上有n个结点,1~n-m为信号传送器,n-m+1~n为观众,当信号传送给观众后,观众会付费观看,每铺设一条道路需要一定费用。现在求以1为根,使得收到观众的费用-铺设道路的费用>=0的情况下,能最多给多少个观众观看?
思路:树形dp,dp[i][j]表示以i为根的子树中选择j个观众(叶子)最大的收益。
①如果当前结点为叶子结点,那么其dp[i][0]=0,dp[i][1]=val[i].
②如果为其他结点,则dp[i][j]=max(dp[i][j],dp[i][j-k]+dp[son][k]-cost)(i:max->0;j:0->max)(表示当前结点选j个叶子,其中k个叶子来自son这棵子树).每个结点的容量为以其为根的子树的所有叶结点数目,可以通过dp回溯累加。

1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cstring> 5 using namespace std; 6 const int maxn = 3010; 7 const int maxe = 6020; 8 const int INF = 0x3f3f3f; 9 int wson[maxn];//以i为根的子树的容量(可选的叶子数目) 10 int val[maxn]; 11 struct edge 12 { 13 int from, to, cost, next; 14 edge(int ff=0,int tt=0,int cc=0,int nn=0):from(ff),to(tt),cost(cc),next(nn){ } 15 }Edge[maxe]; 16 int Head[maxn],totedge; 17 int n, m;//1为根,2~n-m为传送器,n-m+1~n为观众(叶子) 18 int dp[maxn][maxn];//dp[i][j]表示以i为子树选择j个叶子的最大v(叶子值-路值) 19 bool vis[maxn];//表示该点是否已经经过 20 21 void Init() 22 { 23 memset(Edge, 0, sizeof(Edge)); 24 memset(Head, -1, sizeof(Head)); 25 memset(dp, -INF, sizeof(dp)); 26 memset(vis, 0, sizeof(vis)); 27 totedge = 0; 28 } 29 30 void DP(int now, int pre) 31 { 32 vis[now] = true; 33 if (now >= n - m + 1) 34 {//如果为叶子 35 dp[now][0]=0,dp[now][1] = val[now], wson[now] = 1; 36 return; 37 } 38 else dp[now][0] = 0; 39 for (int e = Head[now]; e != -1; e = Edge[e].next) 40 { 41 int u = Edge[e].to, w = Edge[e].cost; 42 if (vis[u]) continue; 43 DP(u, now); 44 wson[now] += wson[u]; 45 for (int j = wson[now]; j >= 1; j--) 46 {//枚举当前结点选择叶子个数(从大往小,01背包) 47 for (int k = 1; k <= min(wson[u],j); k++) 48 {//枚举子节点的叶子个数 49 dp[now][j] = max(dp[now][j], dp[now][j - k] + dp[u][k] - w); 50 } 51 } 52 } 53 } 54 55 void AddEdge(int from, int to, int cost) 56 { 57 Edge[++totedge] = edge(from, to, cost,Head[from]); 58 Head[from] = totedge; 59 Edge[++totedge] = edge(to, from, cost, Head[to]); 60 Head[to] = totedge; 61 } 62 63 int main() 64 { 65 while (~scanf("%d%d", &n, &m)) 66 { 67 Init(); 68 for (int i = 1; i <= n - m; i++) 69 { 70 int k; 71 scanf("%d", &k); 72 for (int j = 1; j <= k; j++) 73 { 74 int to, v; 75 scanf("%d%d", &to, &v); 76 AddEdge(i, to, v); 77 } 78 } 79 for (int i = n - m + 1; i <= n; i++) scanf("%d", val + i); 80 DP(1, 0); 81 for (int i = m; i >= 0; i--) 82 { 83 if (dp[1][i] >= 0) 84 { 85 printf("%d ",i); 86 break; 87 } 88 } 89 } 90 return 0; 91 }
2、poj 2486 Apple Tree
题意:有棵树,每个结点上有权值。从结点1出发走K步,求能走到的点的权值之和最大。
思路:dp[i][j][0]表示从结点i走j步不会到i时的最大值,dp[i][j][1]表示从结点i走j步回到结点i时最大值。
①回到结点u:从u出发,要回到u,需要多走两步u-v,v-u,分配给v子树k步,其他子树j-k步,都返回:
dp[u][j][1]=max(dp[u][j][1],dp[u][j-k-2][1]+dp[v][k][1])
②不回到结点u:
先从其他子树回到u,再在子树v走k步,不回到u:
dp[u][j][0]=max(dp[u][j][0],dp[u][j-k-1][1]+dp[v][k][0])
或者先走k步遍历子树v回到u,在从u走j-k-2步到其他子树,不回到u
dp[u][j][0]=max(dp[u][j][0],dp[v][k][1]+dp[u][j-k-2][0])
参考:http://www.cnblogs.com/wuyiqi/archive/2012/01/09/2316758.html

1 #include<iostream> 2 #include<algorithm> 3 #include<cstring> 4 #include<cstdio> 5 using namespace std; 6 const int maxn = 110; 7 const int maxk = 210; 8 const int INF = 0x3f3f3f3f; 9 int dp[maxn][maxk][2];//dp[i][j][0]表示从结点i走j步不会到i时的最大值,dp[i][j][1]表示从结点i走j步回到结点i时最大值。 10 //回到结点u:从u出发,要回到u,需要多走两步u-v,v-u,分配给v子树k步,其他子树j-k步,都返回: 11 //dp[u][j][1]=max(dp[u][j][1],dp[u][j-k-2][1]+dp[v][k][1]) 12 //不回到结点u:先从其他子树回到u,再在子树v走k步,不回到u 13 //dp[u][j][0]=max(dp[u][j][0],dp[u][j-k-1][1]+dp[v][k][0]) 14 //或者先走k步遍历子树v回到u,在从u走j-k-2步到其他子树,不回到u 15 //dp[u][j][0]=max(dp[u][j][0],dp[v][k][1]+dp[u][j-k-2][0]) 16 int Head[maxn],totedge; 17 struct edge 18 { 19 int from, to, next; 20 edge(int ff=0,int tt=0,int nn=0):from(ff),to(tt),next(nn){ } 21 }Edge[2*maxn]; 22 23 int N,K; 24 bool vis[maxn]; 25 int val[maxn]; 26 27 void AddEdge(int from, int to) 28 { 29 Edge[++totedge] = edge(from, to, Head[from]); 30 Head[from] = totedge; 31 Edge[++totedge] = edge(to, from, Head[to]); 32 Head[to] = totedge; 33 } 34 35 void Init() 36 { 37 memset(dp, 0, sizeof(dp)); 38 memset(Head, -1, sizeof(Head)); 39 memset(vis, 0, sizeof(vis)); 40 totedge = 0; 41 } 42 43 void DP(int now) 44 { 45 vis[now] = true; 46 for (int u = Head[now]; u != -1; u = Edge[u].next) 47 { 48 int v = Edge[u].to; 49 if (vis[v]) continue; 50 DP(v); 51 for (int j = K; j >= 0; j--) 52 { 53 for (int k = 0; k <= j; k++) 54 { 55 if (j - k - 2 >= 0) 56 { 57 dp[now][j][1] = max(dp[now][j][1], dp[now][j - k - 2][1] + dp[v][k][1]); 58 dp[now][j][0] = max(dp[now][j][0], dp[v][k][1] + dp[now][j - k - 2][0]); 59 } 60 if (j - k - 1 >= 0) dp[now][j][0] = max(dp[now][j][0], dp[now][j - k - 1][1] + dp[v][k][0]); 61 } 62 } 63 64 } 65 } 66 int main() 67 { 68 while (~scanf("%d%d", &N, &K)) 69 { 70 Init(); 71 for (int i = 1; i <= N; i++) 72 { 73 scanf("%d", val+i); 74 //dp[i][0][0] = dp[i][0][1] = val[i]; 75 for (int j = 0; j <= K; j++) dp[i][j][0] = dp[i][j][1] = val[i]; 76 } 77 for (int i = 1; i < N; i++) 78 { 79 int u, v; 80 scanf("%d%d", &u, &v); 81 AddEdge(u, v); 82 } 83 DP(1); 84 //int ans = 0; 85 //for (int i = 0; i <= K; i++) 86 //{ 87 // ans = max(ans, max(dp[1][i][0], dp[1][i][1])); 88 //} 89 printf("%d ", max(dp[1][K][0],dp[1][K][1])); 90 } 91 return 0; 92 }
3、poj 1947 Rebuilding Roads
题意:需要把一棵树拆出一个P结点的新子树,问需要的删除的最少边数为多少?
思路:dp[i][j]表示以i为根的子树形成j个结点的新树需要删去的最少边数。刚开始时,dp[i][1]=以i为根的子树的子树个数(即i的孩子的个数),即当做删除所有孩子。然后,dp[now][mv] = min(dp[now][mv], dp[now][mv - k]-1 + dp[u][k]),表示当考虑其u孩子形成的子树时,需要dp[now][mv - k]-1,因为之前所有dp操作都没有涉及当前子树(即表示删除该子树),现在考虑该子树时需要把连接子树和父结点的边添加回去,即删除的边数-1。

1 #include<iostream> 2 #include<algorithm> 3 #include<cstdio> 4 #include<cstring> 5 using namespace std; 6 7 int n, p; 8 const int maxn = 160; 9 const int INF = 0x3f3f3f3f; 10 struct edge 11 { 12 int from, to, next; 13 edge(int ff=0,int tt=0,int nn=0):from(ff),to(tt),next(nn){ } 14 }Edge[maxn*2]; 15 int Head[maxn], totedge; 16 int dp[maxn][maxn];//dp[i][j]表示以i为根的子树形成j个结点的新树需要删去的最少边数 17 bool vis[maxn]; 18 19 void Init() 20 { 21 memset(dp,INF, sizeof(dp)); 22 for (int i = 1; i <= n; i++) dp[i][1] = 0; 23 memset(Head, -1, sizeof(Head)); 24 memset(Edge,0, sizeof(Edge)); 25 memset(vis, 0, sizeof(vis)); 26 totedge = 0; 27 } 28 29 void addEdge(int from, int to) 30 { 31 Edge[++totedge] = edge(from, to, Head[from]); 32 Head[from] = totedge; 33 Edge[++totedge] = edge(to, from, Head[to]); 34 Head[to] = totedge; 35 } 36 37 void DP(int now) 38 { 39 vis[now] = true; 40 41 for (int i = Head[now]; i != -1; i = Edge[i].next) 42 { 43 int u = Edge[i].to; 44 if (vis[u]) continue; 45 DP(u); 46 for (int mv = p; mv >= 1; mv--) 47 { 48 for (int k = 1; k < mv; k++) 49 {//枚举加该子树的结点数 50 dp[now][mv] = min(dp[now][mv], dp[now][mv - k]-1 + dp[u][k]);//因为之前并没有添加该子树,所以删除的边数-1,表示添加该子树 51 } 52 } 53 } 54 } 55 int main() 56 { 57 while (~scanf("%d%d", &n, &p)) 58 { 59 Init(); 60 for (int i = 1; i < n; i++) 61 { 62 int from, to; 63 scanf("%d%d", &from, &to); 64 addEdge(from, to); 65 dp[from][1]++; 66 } 67 DP(1);//假设1为根结点 68 int ans = INF; 69 for (int i = 1; i <= n; i++) 70 { 71 if(i==1)ans = min(ans, dp[i][p]); 72 else ans = min(ans, dp[i][p] + 1);//如果非根节点,因为还要删去和父亲所连接的一条边,+1 73 } 74 printf("%d ", ans); 75 } 76 return 0; 77 }