Tree Dp专题整理
来源:互联网 发布:甩手掌柜工具箱软件 编辑:程序博客网 时间:2024/05/01 03:11
Tree Dp介绍及做题大体思路
Tree Dp题目
一般的tree dp
1. HDU 2196 Computer
思路:两次dfs,第一次是建树,并且求出经过它儿子节点的最长路和次长路,(两条路不能都经过这一个节点的同一个儿子),第二次 寻找从父亲节点过来到这个节点的最长的路。最后求max值即可
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>using namespace std;const int MAXN=10010;struct node{int id,n;//该节点往下的距离和对应的序号}mmax[MAXN],smmax[MAXN];//最大和次大struct EDGE{ int to,next,w;}edge[MAXN*2];int head[MAXN],tot;void add(int a,int b,int w){edge[tot].to=b; edge[tot].w=w; edge[tot].next=head[a]; head[a]=tot++;}void dfs_cal(int u,int fa)//求结点u往下到叶子结点的最大距离,fa是u的父亲结点{mmax[u].n=smmax[u].n=0;for(int i=head[u];i!=-1;i=edge[i].next){int to=edge[i].to;if(to==fa)continue;dfs_cal(to,u);if(smmax[u].n<mmax[to].n+edge[i].w) { smmax[u].n=mmax[to].n+edge[i].w; smmax[u].id=to; if(smmax[u].n>mmax[u].n) swap(smmax[u],mmax[u]); }}}void dfs(int u,int fa){for(int i=head[u];i!=-1;i=edge[i].next){int to=edge[i].to;if(to==fa) continue;if(to==mmax[u].id){if(edge[i].w+smmax[u].n>smmax[to].n) { smmax[to].n=edge[i].w+smmax[u].n; smmax[to].id=u; if(smmax[to].n>mmax[to].n) swap(smmax[to],mmax[to]); } } else { if(edge[i].w+mmax[u].n>smmax[to].n) { smmax[to].n=edge[i].w+mmax[u].n; smmax[to].id=u; if(smmax[to].n>mmax[to].n) swap(smmax[to],mmax[to]); }}dfs(to,u);}}void init(){tot=0;memset(head,-1,sizeof(head));}int main(){int n,to,w;while(cin>>n){init();for(int i=2;i<=n;i++){scanf("%d%d",&to,&w);add(i,to,w);add(to,i,w);}dfs_cal(1,-1);dfs(1,-1);for(int i=1;i<=n;i++)printf("%d\n",mmax[i].n);}return 0;}
2.HDU 1520 Anniversary party
思路:定义dp[ i ][ 2 ](0表示不选,1表示不选),从叶子节点往根结点不断更新dp[ i ][ 0 ]和dp[ i ][ 1 ],状态转移即为
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int MAXN=6010;struct node{ int to,next; }edge[2*MAXN];int tot,head[MAXN];int base[MAXN];int vis[MAXN];int dp[MAXN][2];void add(int u,int v){edge[tot].to=v;edge[tot].next=head[u];head[u]=tot++;}void dfs(int root){dp[root][0]=0;dp[root][1]=base[root];for(int i=head[root];i!=-1;i=edge[i].next){int to=edge[i].to;dfs(to);dp[root][0] += max(dp[to][0],dp[to][1]);dp[root][1] += dp[to][0];}}void init(){tot=0;memset(head,-1,sizeof(head));memset(vis,0,sizeof(vis));memset(dp,0,sizeof(dp));}int main(){int n,a,b,t;while(cin>>n){for(int i=1;i<=n;i++)scanf("%d",&base[i]);init();while(scanf("%d%d",&a,&b),a||b){vis[a]=1;add(b,a);} for(int i=1;i<=n;i++) if(!vis[i]) { t=i;break; }dfs(t); cout<<max(dp[t][0],dp[t][1])<<endl;}return 0;}
3.POJ 1741 Tree
3. 把距离都算出来之后,要快速找到方案数,做法是对距离序列排序,然后找头尾两个数,如果符合情况,算中间的个数,然后从头的下一个开始算,如果不符合情况说明太大,要从尾的前一个开始计算,直到头等于尾。计算复杂度是O(n),sort是O(nlogn)。
4. 还有一个问题在于如果树是一条链,要算n层那么复杂度变为O(n^2logn)。考虑到以任何点点为根的计算都不影响其他点计算,那么每次都都找树的重心即可解决这一特殊状况。
代码:
#include <iostream>#include <algorithm>#include <cstring>#include <string>#include <cstdio>const int MAXN=11111;using namespace std; struct EDGE{ int v,w,next;} edge[5*MAXN];int head[MAXN],cnt;int n,k,vis[MAXN],ans,root,num;void init(){ memset(vis,0,sizeof(vis)); memset(head,-1,sizeof(head)); cnt=ans=0;}void add(int u,int v,int w){ edge[cnt].v=v; edge[cnt].w=w; edge[cnt].next=head[u]; head[u]=cnt++;}int mx[MAXN],size[MAXN],mi,dis[MAXN];void dfssize(int u,int fa)//处理子树的大小{ size[u]=1; mx[u]=0; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v!=fa && !vis[v]) { dfssize(v,u); size[u] += size[v]; if(size[v]>mx[u]) mx[u]=size[v]; } }}void dfsroot(int r,int u,int fa) //求重心 { if(size[r]-size[u] > mx[u]) mx[u]=size[r]-size[u]; if(mx[u]<mi) mi=mx[u],root=u; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v!=fa && !vis[v]) dfsroot(r,v,u); } }void dfsdis(int u,int d,int fa) //求距离 { dis[num++]=d; for(int i=head[u];i!=-1;i=edge[i].next) { int v=edge[i].v; if(v!=fa && !vis[v]) dfsdis(v,d+edge[i].w,u); }}int calc(int u,int d) { int ret=0; num=0; dfsdis(u,d,0); sort(dis,dis+num); int i=0,j=num-1; while(i<j) { while(dis[i]+dis[j]>k && i<j) j--; ret +=j-i; i++; } return ret;}void dfs(int u){ mi=n; dfssize(u,0); dfsroot(u,u,0); ans += calc(root,0); vis[root] = 1; for(int i=head[root];i!=-1;i=edge[i].next) { int v=edge[i].v; if(!vis[v]) { ans -= calc(v,edge[i].w); dfs(v); } }}int main() { while(cin>>n>>k && n && k) { init(); int u,v,w; for(int i=1;i<n;i++) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } dfs(1); cout<<ans<<endl; } return 0;}
4. POJ 3162 Walking Race
题意:一张n个节点的树形地图。要跑步n天,每次都从一个结点开始跑步,每次都要跑到最远的那个结点,两天跑的最远距离有个差值,现在要从这n天里去若干天使得这些天的差值都小于m,问怎么取使得天数最多?n <= 100万,m <= 1亿。思路:树形dp + 线段树或者单调队列。
1. 树形dp思想求每个点到其他某点的最远距离:变成有根树,dfs一遍找出向下的最远距离,再dfs一遍把向上的那条分支也算进来。
2. 上一步得到一个数组,现在要从这个数组里找出连续的一段序列最大值最小值之差小于m,并且长度尽量大。
考虑O(N)的算法,因为是找最长的一个序列,我们可以通过维护两个指针来完成这个计算过程,两个指针表示区间的开始和结束,如果这个区间差值=<m,那么区间尾可以下移,如果>m那么区间必须缩小一些,区间头向后移,这样就可以通过不断增大区间头使得这个区间差值<=m.
3. 找区间中的最大值最下值:可以用线段树啊,很常规的单点查询线段树,好写好想,或者可以用单调队列。
代码:
//线段树#include <iostream>#include <cstring>#include <cstdio>#include <algorithm>#include <queue>using namespace std;const int N = 1000009; int dw1[N];//向下最大 int dw2[N];//向下次大 int up[N];//向上最大 int m[2][N<<2];//线段树的最值 int n, mm;vector<pair<int,int> >v[N];void dfs1(int s)//向下最大和向下次大 { dw1[s]=dw2[s]=0; int ss,dd; for(int i=0;i<v[s].size();i++) { ss=v[s][i].first; dfs1(ss); dd=v[s][i].second+dw1[ss]; if(dd >= dw1[s]) { dw2[s]=dw1[s]; dw1[s]=dd; } else dw2[s]=max(dw2[s],dd); }} void dfs2(int s,int pre,int len)//向上最大 { int ss,dd; if(dw1[pre]==dw1[s]+len) up[s]=len+max(up[pre],dw2[pre]); else up[s]=len+max(up[pre],dw1[pre]); for(int i=0;i<v[s].size();i++) { ss=v[s][i].first; dd=v[s][i].second; dfs2(ss,s,dd); }} void build(int l,int r,int k) { if(l==r) { m[0][k]=m[1][k]=max(up[l],dw1[l]); return; } int mid=(l+r)>>1,ls=k<<1,rs=k<<1|1; build(l, mid, ls); build(mid+1, r, rs); m[0][k]=min(m[0][ls],m[0][rs]); m[1][k]=max(m[1][ls],m[1][rs]);}int query(int ll,int rr,int l,int r,int k,int id){ if(ll==l && rr==r) return m[id][k]; int mid=(l+r)>>1,ls=k<<1,rs=k<<1|1; if(rr<=mid) return query(ll, rr, l, mid, ls, id); else if(ll>mid) return query(ll, rr, mid+1, r, rs, id); else if(id==0) return min(query(ll, mid, l, mid, ls, id), query(mid+1, rr, mid+1, r, rs, id)); else return max(query(ll, mid, l, mid, ls, id), query(mid+1, rr, mid+1, r, rs, id)); }int main() { int a,b; while(cin>>n>>mm) { for(int i=1;i<=n;i++) v[i].clear(); for(int i=2;i<=n;i++) { scanf("%d%d",&a,&b); v[a].push_back( make_pair(i, b)); } dfs1(1); dfs2(1,0,0); build(1,n,1); int lft=1,rit=1,ans=1; while(rit<=n) { int t0 = query(lft, rit, 1, n, 1, 0); int t1 = query(lft, rit, 1, n, 1, 1); if(t1-t0<mm) { ans=max(ans, rit-lft+1); rit++; } else lft++; } cout<<ans<<endl; } return 0; }
//单调队列#include <iostream>#include <cstring>#include <cstdio>#include <algorithm>#include <queue>using namespace std;const maxn=100005;struct Edge{ int v,wei,pre;}edge[maxn*2];int head[maxn],tot;int n,m;int dx[maxn],dy[maxn],d[maxn];int qmin[maxn],qmax[maxn]; void add(int u,int v,int wei){edge[tot].v=a;edge[tot].pre=head[u];edge[tot].wei=wei;head[u]=tot++;}void dfs(int u,int fa,int dis,int *d) { for(int i=head[u];i!=-1;i=edge[i].pre) { int v=edge[i].v,wei=edge[i].wei; if(v!=fa) dfs(v,u,d[v]=dis+wei,d); } }void work()//单调队列 { int ans=0,i,j,front1,front2,rear1,rear2; front1=rear1=0; front2=rear2=0; for(i=1,j=1;j<=n;j++) { while(rear1>front1&&d[qmax[rear1-1]]<=d[j]) rear1--; qmax[rear1++]=j; while(rear2>front2&&d[qmin[rear2-1]]>=d[j]) rear2--; qmin[rear2++]=j; if(d[qmax[front1]]-d[qmin[front2]]>m) { ans=max(ans,j-i); while(d[qmax[front1]]-d[qmin[front2]]>m) { i=min(qmax[front1],qmin[front2])+1; while(rear1>front1&&qmax[front1]<i) front1++; while(rear2>front2&&qmin[front2]<i) front2++; } } } ans=max(ans,j-i); printf("%d\n",ans); }void init(){tot=0;memset(head,-1,sizeof(head));}int main(){while(cin>>n>>m){init();int x,y;for(int i=2;i<=n;i++){scanf("%d%d",&x,&y); addEdge(i,x,y); addEdge(x,i,y);}dfs(1,0,d[1]=0,d);x=1; for(int i=2;i<=n;i++) if(d[i]>d[x]) x=i; dfs(x,0,dx[x]=0,dx); y=1; for(int i=2;i<=n;i++) if(dx[i]>dx[y]) y=i; dfs(y,0,dy[y]=0,dy); for(int i=1;i<=n;i++) d[i]=max(dx[i],dy[i]); work(); }return 0;}
5.POJ 2152 Fire
题意:给定n个节点组成的树,树有边权,要在一些点上建立消防站,每个点 i 建站都有个cost[ i ],如果不在当前的点上建站,也要依赖其他的消防站,并且距离不超过limit[ i ]。求符合上述条件的最小费用建站方案(n<=1000)思路:复杂度为O(n^2)的树形DP,据说比较罕见。
因为要依赖其他站点,所以不仅仅只从子树中获取信息,也可能从父亲结点,兄弟结点获取信息,在计算每个点时首先想到枚举。
dp[ i ][ j ]表示i点及其子树都符合情况下i点依赖j点的最小花费,best[ i ]表示以i为根的子树符合题目要求的最小花费。
因为 i 的每个子节点可以和 i 一样依赖j结点,花费是dp[ k ][ j ] - cost[ j ],或者依赖以k为根的树中的某点,花费是best[ k ],最后一定要加上cost[ j ],因为要在 j 结点建站所以要增加花费。所以状态转移:dp[ i ][ j ] = cost[ j ] + sum(min(dp[ k ][ j ] - cost[ j ],best[ k ])) (k为i的子节点,j为枚举的n个点)
#include <cstdio>#include <vector>#include <cstring>#include <iostream>using namespace std;#define MAX 1100#define INF 2147483647struct node{ int v,len;} now;vector<node> tree[MAX];int n,cur,best[MAX],dp[MAX][MAX];int dist[MAX][MAX],limit[MAX],cost[MAX];void init(){ for(int i=0;i<=n;i++) { tree[i].clear(); best[i]=INF; } for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) dp[i][j]=INF;}void dfs_dist(int s,int pa,int dis)//记录每个点到其他点的距离{ dist[cur][s]=dis; for(int i=0;i<tree[s].size();i++) { int v=tree[s][i].v,len=tree[s][i].len; if(v==pa) continue; dfs_dist(v,s,dis+len); }}void dfs(int s,int pa){ for(int i=0;i<tree[s].size();i++) if(tree[s][i].v!=pa) dfs(tree[s][i].v,s); for(int i=1;i<=n;i++)//枚举 if(dist[s][i]<=limit[s]) { dp[s][i]=cost[i]; for(int j=0;j<tree[s].size();j++)//把子树信息汇总到当前点 { int v=tree[s][j].v; if(v==pa) continue; dp[s][i]+=min(dp[v][i]-cost[i],best[v]); } best[s]=min(best[s],dp[s][i]);//状态转移方程,结果存储在best中 }}int main(){ int a,b,c,t; cin>>t; while(t--) { cin>>n; init(); for(int i=1;i<=n;i++) scanf("%d",&cost[i]); for(int i=1;i<=n;i++) scanf("%d",&limit[i]); for(int i=1;i<n;i++) { scanf("%d%d%d",&a,&b,&c); now.v=b,now.len=c; tree[a].push_back(now); now.v=a,now.len=c; tree[b].push_back(now); } for(int i=1;i<=n;i++) { cur=i; dfs_dist(i,0,0); } dfs(1,0); printf("%d\n",best[1]); } return 0; }
6. POJ 1848 Tree
思路:特别难想一道题,想出来后特别好写
dp[u][0],所有的点都在环内 ,dp[u][1],除了u外其余的都在环内 ,dp[u][2],以u除了u和与u点相连的一条链以外,四种转移:
A.根R的所有子树自己解决(取状态0),转移到R的状态1。即R所有的儿子都变成每个顶点恰好在一个环中的图,R自己不变。
B.根R的k-1个棵树自己解决,剩下一棵子树取状态1和状态2的最小值,转移到R的状态2。剩下的那棵子树和根R就构成了长度至少为2的一条链。
C.根R的k-2棵子树自己解决,剩下两棵子树取状态1和状态2的最小值,在这两棵子树之间连一条边,转移到R的状态0。
D.根R的k-1棵子树自己解决,剩下一棵子树取状态2(子树里还剩下长度至少为2的一条链),在这棵子树和根之间连一条边,构成一个环,转移到R的状态0。
代码:
#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <vector>using namespace std;#define MAXN 105 #define INF 10000 vector<int> edge[MAXN*2];int vis[MAXN];int dp[MAXN][3]; void dfs(int u){vis[u]=1; vector<int> temp; int sum=0,v; for(int i=0;i<edge[u].size();i++) { v=edge[u][i]; if(vis[v]==0) { dfs(v); temp.push_back(v); sum+=dp[v][0];//所有dp[v][0]的和 } } if(temp.size()==0)//已经到子节点 { dp[u][1]=0; dp[u][0]=dp[u][2]=INF; return; } dp[u][1]=min(INF,sum);//只有根节点不在环内 dp[u][2]=dp[u][0]=INF; for(int i=0;i<temp.size();i++)//有一个子节点不在环内 { v=temp[i]; dp[u][2]=min(dp[u][2],sum-dp[v][0]+min(dp[v][1],dp[v][2]));//其余自己处理 dp[u][0]=min(dp[u][0],sum-dp[v][0]+dp[v][2]+1);//以该子节点有一条链,将此链连到以u为根的树上,即+1 } for(int i=0;i<temp.size();i++)//有两个不在环内,将这两个加一条边连起来 { v=temp[i]; for(int j=0;j<temp.size();j++) { if(i==j) continue; int k=temp[j]; dp[u][0]=min(dp[u][0],sum-dp[v][0]-dp[k][0]+min(dp[v][1],dp[v][2])+min(dp[k][1],dp[k][2])+1); } }}int main(){int n,a,b;cin>>n;for(int i=1;i<=n;i++){scanf("%d%d",&a,&b);edge[a].push_back(b);edge[b].push_back(a);}dfs(1);if(dp[1][0]!=INF)cout<<dp[1][0]<<endl;elsecout<<-1<<endl;return 0;}
7.POJ 2486 Apple Tree
题意:节点有权值,在树上走k步,求最大的权值和
思路:dp[root][k][ flag ]表示在子树root中最多走k步,flag是1表示回到root处,flag为0则为不回root。则有如下转移:
A. 从s出发,要回到s,需要多走两步s-t,t-s,分配给t子树k步,其他子树j-k步,都返回
dp[root][j][0]=MAX(dp[root][j][0],dp[root][j-k][0]+dp[son][k-2][0]);
B. 先遍历s的其他子树,回到s,遍历t子树,在当前子树t不返回,多走一步
dp[root][j]][1]=MAX(dp[root][j][1],dp[root][j-k][0]+dp[son][k-1][1]);
C.不回到s(去s的其他子树),在t子树返回,同样有多出两步
dp[root][j][1]=MAX(dp[root][j][1],dp[root][j-k][1]+dp[son][k-2][0]);
代码:
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;struct node{int u,v,val,next;} tree[505];int dp[205][405][2],head[205],val[205];int len,n,k;void add(int u,int v){tree[len].u=u;tree[len].v=v;tree[len].next=head[u];head[u]=len++;}void dfs(int root,int mark){for(int i=head[root];i!=-1;i=tree[i].next){int son=tree[i].v;if(son==mark)continue;dfs(son,root);for(int j=k;j>=1;j--)for(int t=1;t<=j;t++){dp[root][j][0]=max(dp[root][j][0],dp[root][j-t][1]+dp[son][t-1][0]);dp[root][j][0]=max(dp[root][j][0],dp[root][j-t][0]+dp[son][t-2][1]);dp[root][j][1]=max(dp[root][j][1],dp[root][j-t][1]+dp[son][t-2][1]);}}}int main(){int a,b;while(~scanf("%d%d",&n,&k)){memset(dp,0,sizeof(dp));memset(head,-1,sizeof(head));for(int i=1;i<=n;i++){scanf("%d",&val[i]);for(int j=0;j<=k;j++)dp[i][j][0]=dp[i][j][1]=val[i];}len=0;for(int i=1;i<n;i++){scanf("%d%d",&a,&b);add(a,b);add(b,a);}dfs(1,0);cout<<max(dp[1][k][0],dp[1][k][1])<<endl;}return 0;}
树上背包问题
1.POJ 1947 Rebuilding Roads
考虑其儿子k
A.不去掉k子树 dp[ s ][ i ] = min( dp[ s ][ j ] + dp[ k ][ i-j ] ) 0 <= j <= i
B.去掉k子树 dp[ s ][ i ] = dp[ s ][ i ]+1
总: dp[ s ][ i ] = min (min(dp[ s ][ j ] + dp[ k ][ i-j ] ) ,dp[ s ][ i ] + 1 )
#include <iostream> #include <cstring>#include <cstdio>using namespace std; #define MAXN 155#define INF 0x3fffffffint dp[MAXN][MAXN];int son[MAXN],brother[MAXN];int root,n,p;bool father[MAXN];void dfs(int s){for(int i=1;i<=n;i++)dp[s][i]=INF;dp[s][1]=0;int k=son[s];while(k){dfs(k);for(int i=p;i>=1;i--){int temp=dp[s][i]+1;//去掉此子树for(int j=1;j<i;j++)//不去掉此子树temp=min(dp[k][i-j]+dp[s][j],temp);dp[s][i]=temp;}k=brother[k];}}int main(){while(cin>>n>>p){memset(father,0,sizeof(father)); memset(son,0,sizeof(son)); int x,y;for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); brother[y]=son[x];//记录兄弟节点 son[x]=y;//记录子节点 father[y]=1;//记录该点有父亲节点 } for(int i=1;i<=n;i++) if(!father[i]) {root=i; break; } dfs(root); int ans=dp[root][p]; for(int i=1;i<=n;i++) ans=min(ans,dp[i][p]+1);//除根节点需断与父节点的边 cout<<ans<<endl;}return 0;}
2.HDU 1011 Starship Troopers
(留下的士兵不可以再去攻打其他的洞,且必须攻打了前面的洞才可以攻打后面的洞)。问花费m个士兵可以得到的最大价值
思路:dp方程:dp[ p ][ j ] = max(dp[ p ][ j ],dp[ p ][ j-k ] + dp[ son[ p ] ][ k ])
#include <iostream>#include <vector>#include <cstring>#include <cstdio>using namespace std;const int maxn=110;int n,m;int cost[maxn],weg[maxn];int dp[maxn][maxn];bool vis[maxn];vector<int> dv[maxn];void init(){for(int i=0;i<=n;i++)dv[i].clear(); memset(dp,0,sizeof(dp)); memset(vis,0,sizeof(vis)); }void dfs(int p){int temp=(cost[p]+19)/20;//1也需要一个士兵来打 for(int i=temp;i<=m;i++) dp[p][i]=weg[p]; vis[p]=1; for(int i=0;i<dv[p].size();i++) { int t=dv[p][i]; if(vis[t]) continue; dfs(t); for(int j=m;j>=temp;j--) for(int k=1; k<=j-temp; k++)//留下temp攻打p dp[p][j]=max(dp[p][j],dp[p][j-k]+dp[t][k]); }}int main(){while(cin>>n>>m){if(n==-1 && m==-1)break;init();for(int i=1;i<=n;i++) scanf("%d%d",&cost[i],&weg[i]); for(int i=1;i<n;i++) { int u,v; scanf("%d%d",&u,&v); dv[u].push_back(v); dv[v].push_back(u); } if(m==0) { cout<<0<<endl; continue; } dfs(1); cout<<dp[1][m]<<endl;}return 0;}
3.POJ 1155 TELE
转移:dp[ i ][ j ] = max(dp[ i ][ j ] ,dp[ i ][ k ] + dp[ son ][ j-k ] - w[ i ][ son ]
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>#define INF 100000000#define MAXN 3005using namespace std;int n,m,len=0;int num[MAXN],dp[MAXN][MAXN],temp[MAXN];struct node{int now,val,next;} tree[3*MAXN];int head[MAXN];void add(int i,int x,int y) { tree[len].now=x; tree[len].val=y; tree[len].next=head[i]; head[i]=len++;}void dfs(int root){for(int i=head[root];i!=-1;i=tree[i].next){int p=tree[i].now;dfs(p);for(int j=1;j<=num[root];j++)temp[j]=dp[root][j];for(int j=0;j<=num[root];j++)for(int k=1;k<=num[p];k++)dp[root][k+j]=max(dp[root][k+j],temp[j]+dp[p][k]-tree[i].val);num[root] += num[p];}}int main(){while(cin>>n>>m){memset(head,-1,sizeof(head));int k,a,b;for(int i=1;i<=n-m;i++){scanf("%d",&k);for(int j=1;j<=k;j++){scanf("%d%d",&a,&b);add(i,a,b);}}for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)dp[i][j]=-INF;for(int i=n-m+1;i<=n;i++){num[i]=1;scanf("%d",&dp[i][1]);}dfs(1);for(int i=m;i>=0;i--)if(dp[1][i]>=0){cout<<i<<endl;break;}}return 0;}
4.HDU 1561 The more,the better
#include <iostream>#include <cstdio>#include <cmath>#include <cstring>#include <algorithm>#include <vector>using namespace std;const int maxn = 205;int w[maxn],dp[maxn][maxn];vector<int> son[maxn];int n,m;void init(){ memset(dp,-1,sizeof(dp)); for(int i=0;i<=n;i++) { dp[i][0]=0; son[i].clear(); }}void dfs(int root){if(!son[root].size()){dp[root][1]=w[root];return;}for(int i=0;i<son[root].size();i++)dfs(son[root][i]);for(int i=0;i<son[root].size();i++) for(int j=m;j>=0;j--) for(int k=0;k<=j;k++) if(dp[son[root][i]][k]!=-1 && dp[root][j-k]!=-1) dp[root][j]=max(dp[root][j],dp[son[root][i]][k]+dp[root][j-k]);if(root!=0){//最后要加上根节点的值,因为要求攻占了根节点才能往下攻占,0节点除外 for(int j=m;j>=0;j--)//处理一下必须攻占根节点才能攻占剩下的子节点的条件,逆序正好可以处理完 if(dp[root][j-1]!=-1) dp[root][j]=dp[root][j-1]+w[root]; }}int main(){while(cin>>n>>m && n && m){init();for(int i=1;i<=n;i++){int k;scanf("%d%d",&k,&w[i]);son[k].push_back(i);}w[0]=0;dfs(0);cout<<dp[0][m]<<endl;}return 0;}
5.HDU 4003 Find Metal Mineral
当num==0的时候,dp[ pos ][ 0 ]表示用一个机器人去走完所有子树,最后又回到pos这个节点
转移:dp[ pos ][ num ] = min∑{ dp[ pos_j ][ num_j ] + w_j }(pos_j是pos的所有儿子)
当num_j==0的时候,特别处理
使用一维数组的分组背包伪代码如下:
for 所有的组 i
for v=V to 0
for 所有的k属于组i
f[ v ] = max{ f[ v ] ,f[ v-c[ k ] ] + w[ k ] }
要把num个机器人分给所有它的儿子,状态太多,用分组背包
#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;#define INF 0x3f3f3f3fint n,s,k;int dp[10001][11]; struct Edge{ int ed,val,nxt;}edge[20001];int cnt,head[10001]; void add_edge(int sta,int ed,int val){ edge[cnt].ed=ed; edge[cnt].val=val; edge[cnt].nxt=head[sta]; head[sta]=cnt++;}void dfs(int pos,int fa){bool flag=0; for(int i=head[pos];~i;i=edge[i].nxt) if(edge[i].ed!=fa) { dfs(edge[i].ed,pos); flag=1; } if(!flag) return;//3个for就对应分组背包的那3个循环 for(int i=head[pos];~i;i=edge[i].nxt) { int t=edge[i].ed; if(t==fa)continue; for(int j=k;j>=0;j--) if(j==0) dp[pos][0]+=dp[t][0]+2*edge[i].val; else { dp[pos][j]+=dp[t][0]+2*edge[i].val; for(int u=1;u<=j;u++) dp[pos][j]=min(dp[pos][j],dp[pos][j-u]+dp[t][u]+u*edge[i].val); } } }void init(){memset(head,-1,sizeof(head)); memset(edge,0,sizeof(edge)); memset(dp,0,sizeof(dp)); cnt=0; }int main(){ while(cin>>n>>s>>k) { init(); for(int i=1;i<n;i++) { int sta,ed,val; scanf("%d%d%d",&sta,&ed,&val); add_edge(sta,ed,val); add_edge(ed,sta,val); } dfs(s,0); printf("%d\n",dp[s][k]); }}
删点或边类
1.POJ 3107 Godfather
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int maxn=50010; int N;int v[maxn*2],next[maxn*2],head[maxn],E;int vis[maxn];int num[maxn],dp[maxn];void add(int a,int b){ v[E]=b; next[E]=head[a]; head[a]=E++;}void dfs_num(int n,int from){ num[n]=1; for(int i=head[n];i!=-1;i=next[i]) { int k=v[i]; if(k==from) continue; dfs_num(k,n); num[n]+=num[k]; }}void dfs_node(int n,int from){ dp[n]=0; for(int i=head[n];i!=-1;i=next[i]) { int k=v[i]; if(k==from) dp[n]=max(dp[n],N-num[n]); else { dp[n]=max(dp[n],num[k]); dfs_node(k,n); } }}void init(){E=0; memset(head,-1,sizeof(head));}int main(){ while(cin>>N) { init(); int u,v; for(int i=1;i<=N-1;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs_num(1,-1); dfs_node(1,-1); int i,k; for(i=k=N;i>=1;i--) if(k>dp[i]) k=dp[i]; for(i=1;i<=N;i++) if(dp[i]==k) { cout<<i; break; } for(i=i+1;i<=N;i++) { if(dp[i]==k) printf(" %d",i); } cout<<endl; } return 0; }
2. POJ 2378 Tree Cutting
思路:dfs建立树,对于节点x,记a[ x ]为包含自己在内 x 的子树中的结点个数。
#include <iostream>#include <cstdio>#include <cstring>#include <vector>#include <algorithm>using namespace std;const int maxn=10005;bool vis[maxn],res[maxn];int a[maxn],half,n;vector<int> v[maxn];int dfs(int x){ a[x]=1; vis[x]=1; bool ok=1; for(int i=0;i<v[x].size();i++) { int j=v[x][i]; if(!vis[j]) { int temp=dfs(j); if(temp>half) ok=0; a[x]+=temp; } } if(ok && n-a[x] <=half) res[x]=1; return a[x];}int main(){ cin>>n; for(int i=1;i<n;i++) { int a,b; scanf("%d%d",&a,&b); v[a].push_back(b); v[b].push_back(a); } half=n/2; vis[1]=1; dfs(1); for(int i=1;i<=n;i++) if(res[i]) cout<<i<<endl; return 0; }
未完待续~~~~
- Tree Dp专题整理
- tree专题
- dp 专题
- DP 专题
- dp 专题
- dp专题
- [dp专题]
- DP专题
- dp专题
- DP专题
- LeetCode专题----Tree
- tree dp
- DP专题之概率DP
- dp专题2--简单dp
- python多线程专题整理
- 技术专题整理
- (树的专题整理)
- 图论专题整理
- 记录make结束时间,其他Linux程序其实也可以
- 第3周-项目1-三角形类的构造函数-有默认参数的构造函数
- python 2.x字符编码显示问题
- HDU-5190-Go to movies
- Apache+tomcat+mod_jk+centos6.2负载均衡集群配置
- Tree Dp专题整理
- 第3周-项目1-三角形类的构造函数-使用参数初始化表对数据成员初始化
- 在线文档预览
- SQLite基本操作的总结详解
- Javascript学习笔记2
- 设置或清除特定的位
- android数据库操作实战
- TCP通信内核参数调优——拥塞窗口
- Android studio 报 multiple dex files define landroid/support/annotation/AnimRes 问题