树形DP:即在树上DP,用dp[i][]...表示以i为根的子树....。常从根DFS,递归转移dp数组。
没有上司的舞会--
给定一棵有点权的二叉树,选择若干点,使得选出的点任意两个点都不相连,且选出的点权和最大。
n<=100000。
状态:dp[i][0/1]表示以i为根的子树满足条件时选出的最大点权和,i节点选1/不选0。
转移方程:
i不选,则i的儿子可选可不选。
dp[i][0]+=max{dp[l[i]][0],dp[l[i]][1]}+max{dp[r[i]][0],dp[r[i]][1]};
i选:则i的儿子一定都不能选。别忘了加上i的点权。
dp[i][1]=dp[l[i]][0]+dp[r[i]][0]+a[i];
dfs(root);
ans=max{dp[root][0],dp[root][1]};
没有上司的舞会
给定一棵有点权的树,选择若干点,使得选出的点任意两个点都不相连,且选出的点权和最大。
n<=100000。
只需将转移方程转换一下即可。其中v表示i的儿子节点
i不选,则i的儿子可选可不选。
dp[i][0]+=max{dp[v][0],dp[v][1]};
i选:则i的儿子一定都不能选。别忘了加上i的点权。
dp[i][1]=∑{dp[v][0]}+a[i];
dfs(root);
ans=max{dp[root][0],dp[root][1]};
没有上司的舞会++
给定一个树套环,选择若干点,使得选出的点任意两个点都不相连,且选出的点权和最大。
n<=100000。
对于简单树套环题目,可以DFS找环上的一条边(即树的DFS遍历,没有遍历到的边一定就是环上的一条边),然后把该边的一个端点作为根,把原图拉成一个树+边(u,v)。然后分情况讨论。
对于该题:我们以环上的边(u,v)的端点u为根,将原图拉成一棵树+(u,v)。
当u不选时,v可选可不选,没有影响。此时边(u,v)没有任何意义。
当u选时,v必须不选。我们可以将dp[v][1]=-INF,即答案绝不会从选v的情况dp[v][1]转移。
分别对两种情况做DFS,最后得出答案。
void dfs1(int x) {
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
dfs1(v);
}
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
dp[x][0]+=max(dp[v][0],dp[v][1]),
dp[x][1]+=dp[v][0];
}
dp[x][1]+=a[x];
}
fa[u]=-1;
dfs1(u);
ans=dp[u][0];
dp.clear(); fa.clear();//清空dp与fa数组
void dfs2(int x)
{
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
dfs2(v);
}
for(int i=head[x];i;i=e[i].nxt){
int v=e[i].to;
dp[x][0]+=max(dp[v][0],dp[v][1]),
dp[x][1]+=dp[v][0];
}
dp[x][1]+=a[x];
if (x==V) dp[x][1]=-INF;
}
fa[u]=-1;
dfs2(u);
ans=max(ans,max(dp[u][0],dp[u][1]))
//dfs2使v一定不能被选,所以u是否选择无所谓
电子眼--
给定一棵二叉树, 选择其中若干节点,设为特殊节点,使得每个节点最近的特殊节点距离<=1,要求选择最少的节点。
n<=100000。
如果一个点离它最近的特殊点距离<=1,则是合法的
dp[i][0/1] 以i为根这棵子树且合法,且i这个点不设/设特殊点,最少需要设置多少特殊点
dp[i][2] 以i为根的子树,i的所有子孙都是合法的,并且i不合法,最少需要设置多少特殊点
考虑三种情况:
dp[x][0] x不是特殊点,但x的儿子中至少有一个是特殊点,其他都合法(x能被儿子照顾到)
dp[x][1] x是特殊点,x的儿子中可以有不是特殊点,也可以有不合法的(x可以照顾儿子)
dp[x][2] x不是特殊点,并且x的儿子都不是特殊点,但都合法(x必须被父亲照顾)
dp[x][0]=min{dp[l[x]][1]+dp[r[x]][0],dp[l[x]][0]+dp[r[x]][1],dp[l[x]][1]+dp[r[x]][1]}
dp[x][1]=min{dp[l[x]][0],dp[l[x]][1],dp[l[x]][2]}+min{dp[r[x]][0],dp[r[x]][1],dp[r[x]][2]}+1;
dp[x][2]=dp[l[x]][0]+dp[r[x]][0];
电子眼
给定一棵树, 选择其中若干节点,设为特殊节点,使得每个节点最近的特殊节点距离<=1,要求选择最少的节点。
n<=100000。
dp[x][1]=∑min{dp[v][0],dp[v][1],dp[v][2]}+1;
dp[x][2]=∑dp[v][0];
dp[x][0]的转移较为麻烦,需要枚举哪个为特殊点。但我们可以采用一种更为巧妙的做法:dp[x][0]=∑min{dp[v][0],dp[v][1]};
其中必定有一个儿子v'从dp[v'][0]转移到了dp[v'][1],这一过程一定会产生差值。
我们可以枚举这一最小差值,然后加入答案中。注意最小差值可能为负数,因此最小差值需要与0取一个max。
void dfs(int x) {
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
dfs(v);
}
// 枚举哪个儿子是特殊点
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
dp[x][0]+=min(dp[v][0],dp[v][1]);
}
int MIN=10000000;
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
MIN=min(MIN,dp[v][1]-dp[v][0]);
}
dp[x][0]+=max(0,MIN);//答案累加最小差值
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
dp[x][1]+=min(dp[v][0],dp[v][1],dp[v][2]);
}
dp[x][1]++;
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
dp[x][2]+=dp[v][0];
}
电子眼++
给定一个树套环, 选择其中若干节点,设为特殊节点,使得每个节点最近的特殊节点距离<=1,要求选择最少的节点。
n<=100000。
同样将原图拉成一个根为u的树+边(u,v)
当u为特殊点 v可以为特殊点,也可以不是,也可以不合法。
将dp[u][0]和dp[u][2]的方案设为不合法,即dp[u][0]=dp[u][1]=INF
ans=min(ans,dp[v][0],dp[v][1],dp[v][2]);
找到环上的边(u,v)
把v当做根拉成一棵树+(u,v)
当u不是特殊点
把u当做根拉成一棵树=(u,v)
v是特殊点
dp[v][0]=dp[v][2]=INF;
ans=max(ans,dp[u][0],dp[u][2]);
v不是特殊点
dp[v][1]=INF;
ans=min(ans,dp[u][0])
最远距离--
给定一棵带边权的二叉树,求最远的两个点距离是多少。
树上两个点的距离一定能分成一段向上走的路径和经过某点后一段向下走的路径。即
从折点向左子树走的距离+从折点向右子树走的距离。
令dp[i]表示以i为根的子树,从i出发到达叶子的最远路径。
转移方程:dp[i]=max{dp[l[i]]+len[i][l[i],dp[r[i]]+len[i][r[i]]};
//相当于以i为折点的半个路径
ans=max{ans,dp[l[i]]+len[i][l[i]]+dp[r[i]]+len[i][r[i]]};
//最终直径为max{ans,左右到叶子的最长路径加起来}
最远距离
给定一棵带边权的树,求最远的两个点距离是多少。
同样令dp[i]表示以i为根的子树,从i出发到达叶子的最远路径。
只不过状态转移方程要换一下。
考虑直径是由两段路径组成,因此我们可以记录从i出发的最长路径与次长路径,答案由这两段路径更新。
void dfs(int x){
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
dfs(v);
}
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
dp[x]=max(dp[x],dp[v]+len[v]);
}
// len[v] v到它父亲的边的长度
以x为转折点,最长+次长来更新答案
int MAX=0,MAX2=0;
for (int i=head[x]; i; i=e[i].nxt){
int v=e[i].to;
if (dp[v]+len[v]>MAX) {MAX2=MAX; MAX=dp[v]+len[v];}
else if (dp[v]+len[v]>MAX2)
MAX2=dp[v]+len[v];
}
ans=max(MAX+MAX2,ans);
}
快餐店---
给定一棵带边权有n个点的树,要求以每个点出发,最远能走到哪里,距离是多少。
n<=1000。
可以枚举每个点DFS找距离最远的点,时间复杂度为O(n^2)。
快餐店--
给定一棵带边权有n个点的树,要求以每个点出发,最远能走到哪里,距离是多少。
n<=100000。
不能O(n)枚举结点分别DFS,只能一次DFS算出答案。
dp1[i] 表示以i为根的子树,从i走到叶子的最远距离
g1[i] dp1[i]是往哪个儿子走的。
dp2[i] 表示以i为根的子树,从i走到叶子的次长距离。
g2[i] dp2[i]是往哪个儿子走的。
up[i] 表示i先往上再往下的最远走的长度。
ans[i]=max{dp1[i],up[i]};
叶子的染色-
给定一棵n个节点的树,给每个节点染黑色或者白色或者透明。选择一个点作为根,已知每个叶子到根最近的有色节点的颜色是黑色还是白色的。构造方案使得有色节点尽可能少。
n<=1000。
令dp[i][0/1]表示以i为根的子树中所有叶子满足条件时,节点i被染成白色0/黑色1 时的最少有色节点数。
不考虑节点i为透明的原因:仔细思考可以发现,一定存在一种最优解i不是透明的,即节点i一定被染色。
对于i被染成白色:
若i的儿子v被染成了白色,那么i为白色时,把v褪色不会使答案变得更差,因此从 dp[v][0]-1转移而来。
若v为黑色,则v不能去掉,否则不满足要求。
dp[i][0]=∑min{dp[v][0]-1,dp[v][1]}+1;
同理,i被染成黑色:
dp[i][1]=∑min{dp[v][1]-1,dp[v][0]}+1;
最终ans=min{dp[root][0],dp[root][1]};
root 为我们枚举的根
叶子的染色
给定一棵n个节点的树,给每个节点染黑色或者白色或者透明。选择一个点作为根,已知每个叶子到根最近的有色节点的颜色是黑色还是白色的。构造方案使得有色节点尽可能少。
n<=100000。
我们发现根其实是不必枚举的。
u=原来的根,v=现在的根
通过dp转移,可知u与v一定是异色的或有一个是透明的。
当u与v异色时,换根后以u,v为根的子树中叶子的位置不会改变,因此对答案无影响。
当u与v中有一个为透明时,换根后相当于将两个点调换了位置,原来在u上的子树与原来在v上的子树互换,答案仍然不变。
因此我们随便选一个非叶子的结点为根进行DFS就行了。