树形DP
【树上最大独立集】
解释:对于图G=(V,E),从V中选尽量多的点为一个集合,使这些点之间没有边相连
方法一 暴搜 枚举每个点选与不选 O(2n)
方法二 贪心
从叶子节点开始,以根为结束,能选的点尽量选
刚开始听这个算法的时候还以为是一个反面教材,直到给出了证明(类似反证吧)
我们设当前点为点U,且点U这时候可以选(即它的儿子都没有被选过)
1.如果点U在最优方案之中,不做考虑
2.如果点U没有在最优方案之中——
如果点U不选,那么按照逻辑,v是选了的
那我们这时候选U,V受到影响不能再选
看似我们改变了整盘棋局,但实际上只是一个棋子的交换,我们只是把u和v的状态交换了一下,那么会不会有影响呢?
- 对于点U,它的子树的状态是已经定了的,即都没有选,那么U的状态变化显然与子树没有关系了
- 对于点V,它从选转为不选,那么接下来的影响显然是可以让V的更多子树的状态变为选(因为不受父亲的影响了,选与不选状态自由),而对于点V的父亲,显然也是状态自由了~
所以综合U V,并且每个点的贡献值均为1的情况来看,U的“选”只会让最终方案“不更劣”
In short 能选则选的方案是成立的
代码思路:
dfs处理一遍,存下深搜顺序
按深搜倒序开始跑(为了先从叶子开始),如果这个点没有被标记,那么就选它,并且标记它的父亲
但是,这种方法是建立在父亲和儿子价值等价的情况下,所以如果点权不同,是不能使用贪心的
方法三 DP
当题目变化为 树上最大权独立集的时候,无法在子树内直接作出决策
存在不确定性——解决不确定性——枚举不确定性
(回到最初的起点~)
事实上,和子树有出现关系冲突的只有根节点,所以只要考虑根节点选与不选的最大值就可以了
例题:没有上司的舞会
dp[x][0/1] 根节点为x的子树的最大值,0/1表示x的选与不选
1.dp[x][0]=max(dp[son][0],dp[son][1])
2.dp[x][1]=dp[son][0]
【树的重心】
解释:对于一个树,把这个点删去后,其余的连通块的最大节点数最小
我们先解决统计连通块节点数的问题
方法简单
dfs过一遍算每个点的总子树节点数s[z],那么对于一个点u,它的父亲v的节点数就是s[v]-s[u]
然后选出max(s[v]-s[u],s[son])中最小的那个点就是了
【树上边权最长路径】
解释:对于一棵树,找到两点,使他们路径上的边权和最大
方向一 LCA
Ⅰ
Ⅱ 每次保存最长链
设d[i]为i节点到叶子的max值
d[i]=max{d[son]}+w[i]
但这样也基本算是枚举了
方向二 端点
Ⅰ最长链往下——思路如上
Ⅱ 最长链往上
如果最长链往上,我们要判断就是到哪一位祖先为转折点
转折点一方面可以理解为停止不动了,到此为止了
另一方面则是表示为到这个发生转折,开始往下
往上的部分好搞,在Ⅰ的基础上乱搞就好
那么往下的呢?
往下的路径一定是要选尽量长的,保证答案的优秀
但是这时候我们发现,如果只选最长的,很有可能会和u-v冲突,也就是v最长链就是u-v,总不能再走一遍吧
所以我们维护一个次长链,如果最长链真的是u-v,那么我们就走次长链下去
以此选出max值
方向三 前缀
例题1
解析1
例题2
共有n门课,每门课有不同的学分,每门课没有或有唯一- 门直接的先修课程。问在修m课的前提下,能够获得的最大学分数是多少?
解析:要点 分组背包DP
dp[i][j]表示点i已选j个的最大学分
O(n3)
【树形背包】
树形背包区别于普通背包的特点就是在于树形,节点之间有父子牵制关系。比方说选了父节点,才能选择子节点
一阶:n3
最基础,最好理解,每次遍历一个点的时候,先遍历它的儿子,然后再对每个儿子做背包
for(int i=0;i){ int v=G[u][i]; dfs(v,u); for(int j=m+1;j>=1;j--)//背包容量 for(int k=m;k>0;k--)//每次的枚举值 if(j-k<1) continue; dp[u][j]=max(dp[u][j],dp[v][k]+dp[u][j-k]); } }
二阶:n2
方法一
一阶的问题呢,主要是出在dfs套背包,太大了,每次还要子节点搞一遍
那么我们对症下药,dfs序我们提前给它搞出来,然后按照dfs序积压就好
但事实上,这样的答案是累积到最后一个点的。
而且,我们可以记录它的下一个兄弟,即当当前这个点不选的时候,就可以把当前的价值先推给它的下一个兄弟
f[i][j]表示到第i个点取j个元素的答案
void Doit_dp() { for (int i = 1; i <= n; ++i) d[dfn[i]] = s[i]; for (int i = 1; i <= n + 1; ++i) for (int j = 0; j <= m; ++j) f[i][j] = -Inf; for (int i = 0; i <= n; ++i) for (int j = 0; j <= min(i, m); ++j) { upt(f[i + 1][j + 1], f[i][j] + d[i]); upt(f[nx[i]][j], f[i][j]); } }
方法二
有些时候,我们的节点数完全没有达到我们设置的背包容量大小,那么为什么要浪费时间去跑呢
所以我们就设置成最大节点数了
这样的话按原来思路是按照树形DP的基本实现,由儿子累加到父亲
for(int i=0;i=1;j--)//背包容量 for(int k=m;k>0;k--)//每次的枚举值 g[j+k]=max(g[j+k],dp[v][k]+dp[u][j-k]); sz[u]+=sz[v]; for(j=1;j<=sz[u];j++) dp[u][j]=max(dp[i][j],g[j]); }
例题4:
给出一棵n个节点的树,每个节点拥有一个颜色ci,现在定义两点之间的距离为其路径上出现过的不同颜色数量
解析:
例题5
给出一个n个节点的树,每个节点有点权ai 求最长的树上路径,满足路径上的节点点权的gcd和不等于1
n<=2e5 ai<=2e5
解析:
gcd不等于1,相当于所有点权中任意两个都不互质,即都能被某个质数整除
放大到全局,就是该树上路径至少能被一个质数p整除
通过这个结论,我们初步推导,f[i][p]表示为以i为根的子树中,能被p整除的最长链
但是,第二维范围太大,所以要考虑优化
我们再回到树上分析,无论以i为子树的答案中,是走lca还是往上传递,都是要经过当前的子树根节点
所以我们的质数p就可以限定在可以整除i的质数中
那么i有多少个质数可以被整除呢?考虑到最大范围为2e5,1-2e5中的数最多有6个(2*3*……*13=30030 2*3*……*17=510510)
每次转移的时候判断儿子和父亲的质因子是否相同,然后转移长度
注意
1.ans和dp的区别,dp[][]的值是以i为结束,而ans还要考虑到lca
2.初始化
3.答案
4.边界条件
5.循环范围——数组边界
例题7
有一颗点数为n的树,有边权。给一个0-n之内的正整数k,你要在这棵树中选择k个点染成黑色,其余为白色。将所有点染色后,你会获得黑点两两之间的距离加上白点两两之间的距离的和的收益
解析:
dp过程中维护或计算最大值
刚开始的问题所在是在于计算两两之间的答案值,但事实上,思维突破点在于v的贡献是已经确定的,黑点确定,白点确定,uv的贡献也是可以确定的了
dp[i][j]以i为根的子树,j个点被染成黑色的最大值,背包DP
例题8
给定一个n个点m条边的无向无环图,在尽量少的节点上放灯,使得所有边都与灯相邻
1.在灯总数最小的前提,2.两盏灯同时照亮边的数量大
解析;
dp[i][j][0/1]以i为根的子树中,用j盏灯且根节点选/不选的最大边数
1.只考虑目标1
2.知道了选的灯数
保证1的情况下算2的答案
1.自定义结构
2.k进制数
最小灯数*k+亮灯边数
我们将max两灯边数转化为min一灯边数,为了使两个条件可以同时取min
dp[u][0]=dp[v][1]+1
dp[u][1]=Σmin{dp[v]1,dp[v][0]+1}+k
数位DP
例题1
1-N中所有含有13且能被13整除的数的个数
解析:
例题2
1-N转化为二进制之后数位中1的总数的乘积
解析:
不妨将题目转化为len_n中二进制位1的总数,数位dp
dp[i][j][0/1]表示第i位数位中有j个1和上界的关系中的方案数
第i位取0:dp[i-1][j][k]——dp[i][j][k|N_i]
第i位取1:dp[i-1][j][k*max(k,n[i])]——dp[i][j+1][k]
ans=∏dp[n][j][0]+dp[n][j][1]
例题3 定义一个十进制数N的权值F(N)为其各个数位乘上2^len之和
给出A B,问0~B中权值不超过F(A)的数的个数
解析:
考虑F(A)和B可以作为上界,加上数的位数没有别的限制
dp[i][j][0/1]第i位,贡献之和为j的方案数
ans=∑dp[0][j][1+0]
例题4
给定两个正整数a和b,求在[a,b]中 的所有整数中有多少个数能够整除它自身的所有非零数位。
解析:
首先易分析是数位DP~
和非0数位有关,所以和1-9的出现有关;再者因为考虑整除,所以还要考虑mod9余数
但是状态数6e9,过不去
我们再考虑,比方说从mod9余数就可以推导到mod3余数
所以9!——lcm(1,2,……,9)=2050
继续由这个思路推导,如果当前已经出现了9,那么3的出现还是否有必要?所以约束可以等价转化为,出现了的数位的lcm|原数%2520,而2520的质因子只有48个
所以状态数再次下降