树型DP,就是在树上进行的DP。由于树固有的递归性质,树型DP一般都是递归进行的。
通过两个经典例题感受一下。
例1:“没有上司的舞会”问题
公司共有n(n≤6000)位员工。公司要举行一个舞会。 为了让到会的每个人不受他的直接上司约束而能玩得开心,公司领 导决定:
如果邀请了某个人,那么一定不会再邀请他的直接的上司,但 该人的上司的上司,上司的上司的上司……都可以邀请。
已知每个人最多有唯一的一个上司。(该模型就是一个树型模型)
公司的每个人参加晚会都能为晚会增添一些气氛,求一个邀请方案, 使气氛值的和最大。
定义状态:
• 定义 f[i][0/1] 表示以 i 为根节点的子树所能得到的最大的气氛值。
• f[i][0] 表示以 i 为子树且未选择 i 号节点;
• f[i][1] 表示以 i 为子树且选择了 i 号节点
• 最终答案就是 max(f[root][0], f[root][1])
状态转移过程:
• 上司不参加,下属可以参加,也可以不参加。 • 上司参加,下属只能不参加。
• 枚举量x为当前节点i到能直接到达的所有的子节点。
代码:使用了链式前向星建树。
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 99999999
using namespace std;
int n,m;
int w[6010],d[6010],head[6010],f[6010][5];
struct edge{
int from,to,w,next;
}e[6010];
int cnt;
void add(int u,int v,int w){
e[++cnt].from =u;
e[cnt].to =v;
e[cnt].w =w;
e[cnt].next =head[u];
head[u]=cnt;
}
void solve(int u){
if(head[u]==0){
f[u][0]=0;
f[u][1]=w[u];
return;
}
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(f[v][0]==0||f[v][1]==0)
solve(v);
f[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
}
f[u][1]+=w[u];
}
int main(){
while(cin>>n&&n!=0){
cnt=0;
memset(head,0,sizeof(head));
memset(d,0,sizeof(d));
memset(f,0,sizeof(f));
for(int i=1;i<=n;i++)scanf("%d",&w[i]);
int l,k;
for(int i=1;i<n;i++){
scanf("%d%d",&l,&k);
add(k,l,1);
d[l]++;
}
int root;
for(int i=1;i<=n;i++)if(d[i]==0){root=i;break;}
solve(root);
int ans=-inf;
ans=max(f[root][0],f[root][1]);
printf("%d\n",ans);
}
return 0;
}
例2:
有一棵树(n≤1e5),树上每个节点都是一座房子,房子里有人, 树的每条边都有一个权值(距离),所有人都要去别人家玩(通过
最短路径), 有两个要求: • 1. 所有人都要去别人的房子(不能不动) • 2. 不能同时有两个人去同一个房子 • 求这些人移动距离总和的最大值 。
4
1 2 3
2 3 2
4 3 2
ans=18
单独考虑每一条边。 • 利用树的性质,我们可以得知,一条边将一棵树划分为两个部分。 • 假定这条边的权值为w,两侧两个部分的节点个数分别为m1和m2。 • 每一条边最多经过多少次? • 假设一边比另一边少,那么就让这些节点全部到另一边去,这样的 答案一定是最优的。
怎样获取对于每个边而言的m1和m2?
• 假定这个树是一棵有根树。 • 有根树的每条边一定是父亲->儿子的关系。 • 对于这条边,m1为以儿子为根的子树的节点数量。 • (m2 = n - m1) • m1在递归的过程中不断计算即可。
本题是hdu-4118。因为递归栈不够大,需要手动增大。
代码:
#pragma comment(linker,"/STACK:102400000,102400000")
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int T,n;
long long ans;
int vis[100010],head[100010];
int min(int a,int b){
return a<b?a:b;
}
struct edge{
int from,to,w,next;
}e[200010];
int cnt;
void add(int u,int v,int w){
e[++cnt].from =u;
e[cnt].to =v;
e[cnt].w =w;
e[cnt].next =head[u];
head[u]=cnt;
}
int dfs(int u){
int tmp=1;
if(head[u]==0)return 1;
for(int i=head[u];i;i=e[i].next ){
int v=e[i].to ;
if(!vis[v]){
vis[v]=1;
int y=dfs(v);
tmp+=y;
ans+=2*e[i].w*min(y,n-y);
}
}
return tmp;
}
int main(){
cin>>T;
for(int t=1;t<=T;t++){
cnt=0,ans=0;
memset(head,0,sizeof(head));
memset(vis,0,sizeof(vis));
cin>>n;
int x,y,z;
for(int i=1;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
add(x,y,z);
add(y,x,z);
}
dfs(1);
printf("Case #%d: %lld\n",t,ans);
}
return 0;
}