动态动态规划 & 全局平衡二叉树 小记
估计这几天是正式学习 ddp,所以特写笔记。
DDP 简介
是这样一类技巧,利用广义的矩阵乘法实现 单点修改权值,动态查询某个点的 DP 值
其核心思想是利用矩阵乘法具有结合律(可以使用数据结构维护)的优势
序列上的 Ddp
先看一个例子:最大子段和,显然我们有
现在我们要支持修改点权,还是要求
那么我们可以维护这样几个量:
也就是有:
那么写成
我们使用线段树维护矩阵乘法,每次单点修改只需要修改这个矩阵,最后求出所有矩阵的积之后就可以求出答案了。
树上的 Ddp
来看一个模板题。
给定一个带点权的树,求其最大带权独立集权值之和
次询问,每次修改一个点的点权,修改永久生效
容易有如下暴力 dp:
设
则显然有:
我们能否将其转化为一个序列上的问题呢?
树链剖分,我们可以利用重剖的优秀性质:一条链至多划分为
那么我们只需要解决:
- 维护重链的矩阵运算结果
- 维护轻儿子(链顶)向上更新的辅助信息。
考虑将树进行轻重链剖分,同时维护
这样做的目的是:保证每个点有唯一前驱,以转化为序列,且容易修改辅助信息的和(至多有
首先我们预处理出
于是就可以知道
这样利用线段树维护区间积就解决了第一个问题。注意转移顺序,是深度大的乘上深度小的转移矩阵,所以线段树上是右区间乘左区间,并且还需要记录链底的编号。
然后我们考虑第二个问题
-
首先更改
的值 -
跳到链顶
-
使用旧 链顶
值,撤销掉对 的 影响 -
取出新的链顶的
值 -
使用 新 链顶
值,加入对 的 影响 -
更新
的转移矩阵
-
-
更新
回到当当前链顶是树根时候,不需要进行
操作
这个题目有一个有趣的性质是叶子节点的转移矩阵第一行可以等效为
一般情况下不具备这个性质时需要单独处理
复杂度是
全局平衡二叉树
这时,你在做完模板题之后发现:你遇到了模板题的加强版:P4751
在数据规模扩大到
如果您认为在下拙作较为晦涩,欢迎左转 Oi-wiki
算法介绍
我们注意到,树链剖分方法里,有一个地方开销是比较大的,就是利用线段树查询一段区间的矩阵积,考虑优化掉它。
有没有什么办法可以
可以的,我们考虑将每一条重链单独拿出,按照深度建立一颗二叉搜索树(这保证了左右子树加树根是一段连续区间),同时树根维护整个子树的矩阵积,这样的话,我们可以
- 跳到二叉树的父亲(假设存在,同一条重链),
pushup
- 跳出重链,类似树剖进行撤销和更新,同时继续往上跳。这一步显然是
的总共,这一步我们可以对每个二叉树根的父亲设置为链顶在原树上的父亲
发现我们只需要保证跳跃总步数是
考虑这样一种剖分策略:定义
考虑你从节点
因此我们保证了跳跃总步数是
建树后是:
我们称 二叉树边为重边,非二叉树边为轻边
建树是容易的,我们递归每一个重链的链顶,提取出这一条重链,对其递归建树,最后解决父亲的关系即可。
int divide(int l,int r){
if(l>r)return 0;
int sum=0;
for(int i=l;i<=r;++i)sum+=lsz[sta[i]];
for(int i=l,t=lsz[sta[l]];i<=r;++i,t+=lsz[sta[i]])if(sum<=t*2){
lc[sta[i]]=divide(l,i-1);
rc[sta[i]]=divide(i+1,r);
if(lc[sta[i]])fa[lc[sta[i]]]=sta[i];
if(rc[sta[i]])fa[rc[sta[i]]]=sta[i];
pushup(sta[i]);
return sta[i];
}
}
int build(int u,int f){
for(int x=u,l=f;x;l=x,x=son[x])for(auto v:e[x])if(v!=l&&v!=son[x])fa[build(v,x)]=x;
top=0;for(int x=u;x;x=son[x])sta[++top]=x;
return divide(1,top);
}
维护 Ddp
我们可以类似于平衡树的方法维护,只不过这个是静态树。
每个点建立两个矩阵
注意合并顺序与矩阵乘法顺序有关系,这一步可以通过 pushup
实现。
void pushup(int x){
mat2[x]=mat1[x];
if(lc[x])mat2[x]=mat2[lc[x]]*mat2[x];
if(rc[x])mat2[x]=mat2[x]*mat2[rc[x]];
}
往上跳的一步,如果没有跳出重链,那么直接 pushup
即可,否则类似树链剖分,撤销并更新
void upd(int x,int v){
mat1[x].a[1][0]+=v-w[x];w[x]=v;
for(int i=x;i;i=fa[i]){
if(lc[fa[i]]!=i&&rc[fa[i]]!=i&&fa[i]){
int t=fa[i];
mat1[t].a[0][0]=mat1[t].a[0][1]=mat1[t].a[0][0]-get2(i);
mat1[t].a[1][0]-=get(i);
pushup(i);
mat1[t].a[0][0]=mat1[t].a[0][1]=mat1[t].a[0][0]+get2(i);
mat1[t].a[1][0]+=get(i);
}
else pushup(i);
}
}
题目选练
首先模板题已经没了。
保卫王国
比较水的题目啊。
首先考虑这玩意是最小点覆盖,如果用全集减去最大独立集来做的话就是模板题了。不过为了练习我们还是选择打一遍朴素 dp。
首先这个修改可以看作是将点权修改为
同样考虑设
改写为矩阵形式是:
改改模板就能过了吧。而且这玩意也不需要特判叶子。
洪水
这个题的主要目的是说明在全局平衡二叉树上如何查找一个点的 dp 值。
显然我们可以设
那么也有:
由于转移涉及
这里可能需要特判叶子。
这里我们考虑全局平衡二叉树如何查找一个节点的 dp 值,整个修改过程是不变的。
譬如我们需要找
int gans(int u){
mat now=mat1[u];if(rc[u])now=mat2[rc[u]]*now;
while(f[u]&&(lc[f[u]]==u||rc[f[u]]==u)){
if(lc[f[u]]==u){
if(rc[f[u]])now=mat2[rc[f[u]]]*mat1[f[u]]*now;
else now=mat1[f[u]]*now;
}
u=f[u];
}
return now.a[0][0];
}
Min-Max搜索 ZJOI2019
注意题目是
那么答案的贡献路径是一条链
那么这条链的任何一个点它断掉之后都可以导致答案变化。
由于代价是
这时候我们可以用一个
-
如果
不在答案链上 为其当前值仍然符合答案不变的要求的方案数,例如 是奇数就是 ,否则是 -
如果
在答案链上, 为当前值不变的方案数。
那么不妨设
因为我们保证了所有值不变,它们并未互相影响。
考虑转移
-
非叶子节点
显然你可以容斥转移,只要所有儿子非法,那么自己就可以取到一个合法解(例如自身取
,这时候所有的儿子是取 ,它们要求 ,只要它们全部 自身就满足 的要求了)。也就是
, 是子树内叶子节点个数 -
叶子节点
根据当前节点自身是否可以让其非法且其大小关系是否合适决定是否可以选择这个点让其合法找方案数即可。注意有情况是选了一定会导致非法。这里稍微分讨就好了。
-
答案链上点
根据上述非叶子节点的分析
- 对于不是答案链上的儿子,乘上
- 对于是答案链上的儿子,乘上
- 对于不是答案链上的儿子,乘上
这样每次暴力更改可以获得 70 高分。
void dp(int u,int fa,int lim){
if(lef[u]){
if(abs(u-wrt)<K&&(lim&1)==(u<wrt))f[u]=1;
else f[u]=((dep[u]&1)==(u<wrt))<<1;
return ;
}
f[u]=1;
for(int v:e[u])if(v!=fa){
dp(v,u,lim);
f[u]=(pw[sz[v]]+p-f[v])%p*f[u]%p;
}
}
void dfs(int u,int fa){
f[u]=1;
for(auto v:e[u])if(v!=fa){
if(wrt==val[v])dfs(v,u),f[u]=f[u]*f[v]%p;
else dp(v,u,dep[u]),f[u]=(pw[sz[v]]+p-f[v])%p*f[u]%p;
}
}
注意到答案实际上是删掉答案链后,所留下的森林里每棵树的
那么我们就可以设
就有:
注意到乘法可能有零而导致撤销出错,可以维护一个
切树游戏 SDOI2017
考虑异或卷积,设
注意
感觉有点像
考虑暴力DP,可以维护
答案就是
可以考虑再维护一个
这样做是
这有个弊端就是你每次都必须要把这个
直接维护这个向量就好了吧,这个向量对应位置
不妨设
那么按常理,维护
而
有:
注意到在
但是修改时怎么撤销呢?预处理所有逆元即可?0 怎么办?。
事实上我们可以像上一个题一样考虑
现在复杂度变成了
不必气馁,我们探求一下这个矩阵有什么性质:
它仍然满足这种形式,可以被
这样就降低到了 4 倍常数,可以通过。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战