树上背包问题小结
by Wine93 2014.1.15
2.训练题目
1.POJ 1155 TELE(树形DP+分组背包) http://poj.org/problem?id=1155
题意:给定一棵边权全为负,叶子结点为正权的有根树! 问从根结点出发使得权值和不为负最多能到达的叶子结点个数。
思路:树形DP+分组背包!
dp[u][j]:以u为跟结点,选j个叶子结点最大价值
dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-e[i].w); (v为u的子结点)
# include<map> # include<set> # include<cmath> # include<queue> # include<stack> # include<vector> # include<string> # include<cstdio> # include<cstring> # include<iostream> # include<algorithm> # include<functional> using namespace std; typedef pair<int,int> PII; # define MOD 1000000007 # define LL long long # define pb push_back # define F first # define S second # define INF 1<<30 # define N 3005 int num,head[N]; int n,m,money[N]; int dp[N][N],leaf[N]; struct edge { int u,v,w,next; }e[N]; void addedge(int u,int v,int w) { e[num].u=u; e[num].v=v; e[num].w=w; e[num].next=head[u]; head[u]=num++; } void Count(int u) { int i,v; if(u>n-m) leaf[u]=1; else leaf[u]=0; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; Count(v); leaf[u]+=leaf[v]; } } void dfs(int u) { int i,j,k,v; if(u>n-m) //是否为叶子结点 { dp[u][1]=money[u]; return; } for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; dfs(v); for(j=leaf[u];j>=1;j--) for(k=1;k<=leaf[v]&&k<=j;k++) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]-e[i].w); } } void init(int n) { int i,j; num=0; memset(head,-1,sizeof(head)); for(i=1;i<=n;i++) for(j=1;j<=n;j++) dp[i][j]=-INF; } int main() { // freopen("in.txt","r",stdin); int i,j,k,u,v,c,ans; while(scanf("%d%d",&n,&m)!=EOF) //n个结点 m个叶子结点 { ans=0; init(n); for(u=1;u<=n-m;u++) { scanf("%d",&k); //k个儿子 for(i=1;i<=k;i++) { scanf("%d%d",&v,&c); addedge(u,v,c); } } for(i=1;i<=m;i++) scanf("%d",&money[n-m+i]); Count(1); dfs(1); for(i=n-1;i>=1;i--) if(dp[1][i]>=0) { ans=i; break; } printf("%d\n",ans); } return 0; }
2.POJ 1947 Rebuilding Roads(树形DP+分组背包+2遍dfs) http://poj.org/problem?id=1947
题意:给定一棵树,求遗留下P个结点的子树,最少需要断几条边
思路:树形DP+分组背包
1.第一步dfs计算出cut[u][i]:以u为结点切除i个结点最少需要断几条边
2.第二次dfs计算出left[u][i]:以u为结点留下i个结点最少需要断几条边
3.遍历所有的顶点,取left[u][p]的最小值就是答案
# include<map> # include<set> # include<cmath> # include<queue> # include<stack> # include<vector> # include<string> # include<cstdio> # include<cstring> # include<algorithm> # include<functional> using namespace std; typedef pair<int,int> PII; # define MOD 1000000007 # define LL long long # define pb push_back # define F first # define S second # define N 205 # define M 305 int num,head[N]; int son[N],cut[N][N],left[N][N]; struct edge { int u,v,next; }e[N]; void addedge(int u,int v) { e[num].u=u; e[num].v=v; e[num].next=head[u]; head[u]=num++; } void getson(int u,int fa) { int i,v; son[u]=1; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; getson(v,u); son[u]+=son[v]; } } void dfs(int u,int fa) { int i,j,k,n,v; cut[u][son[u]]=1; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; dfs(v,u); for(j=son[u];j>=1;j--) for(k=1;k<=son[v]&&k<=j;k++) cut[u][j]=min(cut[u][j],cut[u][j-k]+cut[v][k]); } } //left[u][i]:已u为结点保存i个 void getleft(int u,int fa) { int i,v; if(fa==0) left[u][son[u]]=0; else left[u][son[u]]=1; for(i=1;i<son[u];i++) left[u][i]=cut[u][son[u]-i]+(fa!=0); for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; getleft(v,u); } } void init(int n) { int i,j; num=0; memset(head,-1,sizeof(head)); for(i=1;i<=n;i++) for(j=1;j<=n;j++) cut[i][j]=left[i][j]=1<<30; } int main() { //freopen("in.txt","r",stdin); int i,n,p,u,v,ans; while(scanf("%d%d",&n,&p)!=EOF) { ans=1<<30; init(n); for(i=1;i<n;i++) { scanf("%d%d",&u,&v); addedge(u,v); addedge(v,u); } if(p==n) { printf("0\n"); continue; } getson(1,0); dfs(1,0); getleft(1,0); for(i=1;i<=n;i++) ans=min(ans,left[i][p]); printf("%d\n",ans); } return 0; }
3.HDU 1561 The more, The Better (树形DP+分组背包)http://acm.hdu.edu.cn/showproblem.php?pid=1561
题意:在一棵树上选择若干个点获得的最大价值,选子节点必须先选父节点
思路:
# include<map> # include<set> # include<cmath> # include<queue> # include<stack> # include<vector> # include<string> # include<cstdio> # include<cstring> # include<iostream> # include<algorithm> # include<functional> using namespace std; typedef pair<int,int> PII; # define MOD 1000000007 # define LL long long # define pb push_back # define F first # define S second # define N 205 int num,head[N]; int w[N]; int son[N],dp[N][N]; struct edge { int u,v,next; }e[N]; void addedge(int u,int v) { e[num].u=u; e[num].v=v; e[num].next=head[u]; head[u]=num++; } void getson(int u,int fa) { int i,v; son[u]=1; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; getson(v,u); son[u]+=son[v]; } } void dfs(int u,int fa) { int i,j,k,v,limit=(u!=0); dp[u][1]=w[u]; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; dfs(v,u); for(j=son[u];j>=1;j--) for(k=1;k<=son[v]&&k<=j-limit;k++) dp[u][j]=max(dp[u][j],dp[u][j-k]+dp[v][k]); } } void init() { num=0; memset(head,-1,sizeof(head)); } int main() { //freopen("in.txt","r",stdin); int i,n,m,u,v; while(scanf("%d%d",&n,&m)!=EOF&&(n+m)) { init(); memset(dp,0,sizeof(dp)); for(v=1;v<=n;v++) { scanf("%d%d",&u,&w[v]); addedge(u,v); } getson(0,-1); dfs(0,-1); printf("%d\n",dp[0][m]); } return 0; }
4.ZOJ 3626 Treasure Hunt I(树形DP+分组背包) http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemId=4772
题意:给定一棵结点权值(获得价值),边权(消耗步数)树! 求从k点出发最多走m步并且回到k点获得的最大价值和!
思路:简单树形背包 dp[u][i]:以u为结点走i步并且回到u的最大价值和!
# include<map> # include<set> # include<cmath> # include<queue> # include<stack> # include<vector> # include<string> # include<cstdio> # include<cstring> # include<iostream> # include<algorithm> # include<functional> using namespace std; typedef pair<int,int> PII; # define MOD 1000000007 # define LL long long # define pb push_back # define F first # define S second # define N 105 # define M 205 int num,head[N]; int n,s,m,w[N]; //n:结点 s:出发点 m:步数 int dp[N][M]; //dp[i][j]:i结点走j步回到i的最大价值 struct edge { int u,v,w,next; }e[M]; void addedge(int u,int v,int w) { e[num].u=u; e[num].v=v; e[num].w=w; e[num].next=head[u]; head[u]=num++; } void dfs(int u,int fa) { int i,j,k,v; for(i=0;i<=m;i++) dp[u][i]=w[u]; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; dfs(v,u); for(j=m;j>=2*e[i].w;j--) // 走j步 for(k=0;k<=j-2*e[i].w;k++) dp[u][j]=max(dp[u][j],dp[u][j-k-2*e[i].w]+dp[v][k]); } } void init() { num=0; memset(head,-1,sizeof(head)); memset(dp,0,sizeof(dp)); } int main() { int i,u,v,c; while(scanf("%d",&n)!=EOF) { init(); for(i=1;i<=n;i++) scanf("%d",&w[i]); for(i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&c); addedge(u,v,c); addedge(v,u,c); } scanf("%d%d",&s,&m); dfs(s,0); printf("%d\n",dp[s][m]); } return 0; }
5.POJ 3345 Bribing FIPA(树形DP+分组背包) http://poj.org/problem?id=3345
题意:给定N个国家,相互之间可能存在附属关系,现在想要至少贿赂m个国家.已知,贿赂一个国家,那么如果该国家拥有附属国,那么他所有附属国都可以算作已经贿赂,求最小贿赂值。
思路:简单树形背包! dp[u][i]:以u为结点贿赂i个国家最少的的话费
注意:题目给的是森林,必须要自己合成树(而不是在森林中找满足个数的最小值,有可能出现个数不足的情况,所以必须得合成树再进行DP)
# include<map> # include<set> # include<cmath> # include<queue> # include<stack> # include<vector> # include<string> # include<cstdio> # include<cstring> # include<iostream> # include<algorithm> # include<functional> using namespace std; typedef pair<int,int> PII; # define MOD 1000000007 # define LL long long # define pb push_back # define F first # define S second # define N 205 int num,head[N]; int n,m,w[N]; //n个国家 至少m个国家支持 int son[N],dp[N][N],vis[N]; int father[N]; int cnt; //Hash长度 map<string,int> Hash; struct edge { int u,v,next; }e[N]; void addedge(int u,int v) { e[num].u=u; e[num].v=v; e[num].next=head[u]; head[u]=num++; } void getson(int u,int fa) { int i,v; son[u]=1; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; getson(v,u); son[u]+=son[v]; } } void dfs(int u,int fa) { int i,j,k,v; for(i=head[u];i!=-1;i=e[i].next) { v=e[i].v; if(v==fa) continue; dfs(v,u); for(j=son[u];j>=1;j--) //获得j个国家的支持需要的砖石 for(k=1;k<=son[v]&&k<=j;k++) dp[u][j]=min(dp[u][j],dp[v][k]+dp[u][j-k]); } if(u) for(i=1;i<=son[u];i++) dp[u][i]=min(dp[u][i],w[u]); } void read(char *s) { int u,v,k=0; char *p=strtok(s," "); while(p) { k++; if(k==1) { if(!Hash[p]) u=Hash[p]=++cnt; else u=Hash[p]; } else if(k==2) sscanf(p,"%d",&w[u]); else { if(!Hash[p]) v=Hash[p]=++cnt; else v=Hash[p]; father[v]=u; } p=strtok(NULL," "); } } void init() { int i,j; num=0; memset(head,-1,sizeof(head)); cnt=0; Hash.clear(); for(i=0;i<N;i++) for(j=1;j<N;j++) dp[i][j]=1<<30; memset(father,0,sizeof(father)); } int main() { // freopen("in.txt","r",stdin); char s[400005]; int i,ans; while(gets(s)&&s[0]!='#') { sscanf(s,"%d%d",&n,&m); init(); for(i=1;i<=n;i++) { gets(s); read(s); } for(i=1;i<=n;i++) addedge(father[i],i); getson(0,-1); dfs(0,-1); ans=1<<30; for(i=0;i<=n;i++) ans=min(ans,dp[i][m]); printf("%d\n",ans); } return 0; }