树上DP
前置知识:图与树入门、动态规划
树的子树结构有天然有子结构性质,一般是依次来进行动态规划
令 dp[now]
表示 now
节点的子树的信息,然后将一个节点的多个儿子的信息合并
例:
有
现在举办一个舞会,如果一个人的直系上司来了,那么它就不回来,请问最多会来几个人?
令 dp[now][0/1]
表示 now
本人来或不来时,now
的子树内最多来几个人
由儿子的子树转移过来,假设 t
是 now
的儿子
如果 now
来,那么只能 t
不来:dp[now][1]+=dp[t][0]
如果 now
不来,那么 t
来或不来都可以:dp[now][0]+=max(dp[t][0],dp[t][1])
最后记得算上 now
自己的贡献
void dfs(int now,int fa)
{
for(int t:eg[now])
{
if(t==fa) continue;
dfs(t,now);
dp[now][1]+=dp[t][0];
dp[now][0]+=max(dp[t][1],dp[t][0]);
}
dp[now][1]++;//算上自己
}
2
3
4
5
6
7
8
9
10
11
树上背包
我们将背包问题放到树上(又称有依赖的背包)
假设有
选
仍然以子树为子结构,设 dp[now][k]
表示 now
的子树中选了
按照背包的方式进行转移
void dfs(int now,int fa)
{
for(int t:eg[now])
{
if(t==fa) continue;
dfs(t,now);
for(int j=m;j>=1;--j)//一共选了几个物品,注意从大到小,原因在背包那一节讲过
{
for(int k=0;k<=j;++k)//子树选几个
{
dp[now][j]=max(dp[now][j],dp[now][j-k]+dp[t][k]);
}
}
}
for(int j=m;j>=1;--j)
{
dp[now][j]=dp[now][j-1]+val[now];//选子树必须要选自己
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
观察循环和递归可以得出这个是
有一些部分是多余的,没有必要处理,比如我们枚举了上界是
void dfs(int now,int fa)
{
str[now]=0;
for(int t:eg[now])
{
if(t==fa) continue;
dfs(t,now);
for(int j=0;j<=str[now];) tmp[j]=dp[now][j];
for(int j=0;j<=str[now];++j)//之前选几个物品
{
for(int k=0;k<=str[t]&&j+k<=m;++k)//子树选几个物品
{
dp[now][j+k]=min(dp[now][j+k],tmp[now][j]+dp[t][k]);
}
}
str[now]+=str[t];
}
++str[now];
for(int j=m;j>=1;--j)
{
dp[now][j]=dp[now][j-1]+val[now];//选子树必须要选自己
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这样相当于卡死了上下界
这个循环:
for(int j=0;j<=str[now];++j)
{
for(int k=0;k<=str[t]&&j+k<=m;++k)
{
}
}
2
3
4
5
6
7
str[now]
是之前子树的节点个数,str[t]
是 t
子树的节点个数,这两个嵌套起来相当于是在 now
的所有子树里面,任意选两个节点的方案数。
反过来说,任意两个节点也只会在最近公共祖先(LCA)处被合并一次,所以这部分合并的总复杂度是
总复杂度是指所有节点作为 now
时,这两个循环带来的复杂度求和是
还有更严格的上界:
定义子树节点个数超过
极小大子树:没有任何一个儿子是大子树的大子树
极大小子树:父亲是大子树的小子树
假设小子树的大小为
小子树内部互相合并的复杂度
最坏情况下是每一个
极小大子树全都合并起来的复杂度:极小大子树不会超过
大子树和小子树合并的复杂度:
所以总时间复杂度是
P1040 [NOIP2003 提高组] 加分二叉树 树上dp和区间dp的结合
换根DP
先看一个简单的题:
求以
只要求出所有节点的深度,加起来就可以了。
加强一下这个题:求一个节点,以该节点为根时,所有节点的深度之和最大。
考虑另一个问题:
本来根节点在
现在要把根移动到和
假设有
那么
通过
也就是说,可以通过一定的信息,
那么可以先通过一遍遍历,求出以
void dfs1(int now,int fa)
{
str[now]=1;
dep[now]=dep[fa]+1;
sum+=dep[now];
for(int t:eg[now])
{
if(t==fa) continue;
dfs1(t,now);
str[now]+=str[t];
}
}
void dfs2(int now,int fa)
{
ans=max(ans,sum);
for(int t:eg[now])
{
if(t==fa) continue;
sum-=str[t];
sum+=n-str[t];
str[now]-=str[t];//更改一下 now 和 t 的子节点。
str[t]+=str[now];
dfs2(t,now);
str[t]-=str[now];
str[now]+=str[t];
sum+=str[t];
sum-=n-str[t];
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29