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 2147483647
struct 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;
else
cout<<-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 0x3fffffff
int 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 3005
using 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
思路:dp[ i ] [ j ] 以节点i为跟,取j个(包括i,即dp[i][1]=V[i])所能得到的最大值
#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 0x3f3f3f3f
int 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;
}