news 2026/4/22 16:07:28

【模板】动态 dp 学习笔记(树剖版)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【模板】动态 dp 学习笔记(树剖版)

歉:作者是在打代码之前就完成了文字部分,转移方程的锅代码中修了,文字部分没修,在此致歉。

【模板】动态 DP 加强版 题解

该篇为题解。

总文章(动态 dp 学习笔记)同步发表于 cnblogs。

总文章(动态 dp 学习笔记)同步发表于 luogu。

前置知识:

简单 dp

树链剖分

矩阵乘法和广义矩阵乘法

P4719 【模板】动态 DP

本文着重讲下修改的具体过程以及代码实现,蒟蒻花了好长时间才明白。

鏖战一天终于通过了板子题啊啊啊!!!

不带修:简单树上 dp。

考虑不带修改,就是一个平凡的树上最大权独立集问题,简单树上 dp 即可求解。

表示以

为根子树中选

不选

所得到的树上最大权独立集的大小。

转移是容易的:

带修了!

但是丧心病狂的出题人加上了修改点权!

考虑动态维护这个问题。

发现树剖有一个性质:跳重链至多

次。

设:

的重儿子。

点所在重链的链头节点。

的父亲节点。

然后修正我们的 dp 为:

表示以

为根子树选

不选

的答案。

表示以

为根子树不选以重儿子为根子树中(包括重儿子)任何一个点时,选

不选

的答案。

为点

的儿子节点,那么有转移:

转移改为使用广义矩阵乘法:

直接定义广义矩阵乘法

本文并不在广义矩阵乘这里深入展开,具体可参考其他博客。作者还没完全搞懂。

具体可参考 https://www.cnblogs.com/qkhm/p/19055513/ddp。

当然如果你不会证明你也可以直接设三个矩阵然后暴力手算验证该新定义的矩阵是否满足结合律,也是可行的。

发现这个新的矩乘还是满足结合律(不满足交换律),单位矩阵是主对角线上是

,其他全都是

本题单位矩阵为

设原树上点

的答案矩阵

那么我们需要求出每个点的转移矩阵,设为

定义完所有所需矩阵之后,转移可以写为:

写出完整矩阵转移的柿子为

由待定系数法

得出

点的转移矩阵为

那么转移可以写成

那么一条重链上某一点

的答案矩阵(

值)可以用矩阵连乘计算:

链尾节点没有

,所以

手算发现乘

之前的那个

矩阵提出第一列构成一个矩阵后,恰好是乘之后的答案矩阵。所以我们不用真正乘

矩阵,直接提取第一列即可。

初始化:

那么我们就可以在线段树上维护区间矩阵连乘积了。注意矩阵乘法不满足交换律,我的做法在合并区间时需要左

右。

具体实现时比较复杂。

先两次 dfs 进行树链剖分。我们需要额外记录一个

表示一条以

为链首的那条重链的链底。

再来一次 dfs 跑一遍树上 dp 初始化

数组。

在 dfn 序上建线段树,每个叶子节点记录它代表的树上点

的转移矩阵

对于非叶子节点,记录的矩阵为左儿子矩阵乘右儿子矩阵。

查询答案时我们需要查询以根为链首的

矩阵值,可以通过线段树区间查询该重链的矩阵乘积得到。

解决修改问题:

着重讲下修改的具体过程以及代码实现,蒟蒻花了好长时间才明白。

注意,下文对

的修改改的是矩阵里的值,而不是

数组的值。

设我们要将树上点

的权值改为

。设原先点权为

首先开一个全局临时矩阵

,令

然后修改矩阵

的值,也就是矩阵的第二行第一列那个位置的值。容易发现点

的贡献由原先的

变为

,变化量为

,所以我们在矩阵中更改

即可。

然后我们考察

的实际意义,为一个点轻儿子的贡献。考虑有几个点的

值需要更新。发现是

点和跳链过程中每条链链顶的父亲,其他点均不需要修改。

由于每次查询根链最多跳

条重链,所以对应的转移矩阵

只会有

个得到修改,加上线段树的复杂度就是

的。复杂度得到证明。

然后现在节点为

,开始跳链操作。

先线段树区间查询

所在重链的乘积为矩阵

的第一列即为修改之前链顶的答案矩阵的值(

值)。

然后线段树单点修改

点的转移矩阵,将其变为

再线段树区间查询

所在重链的乘积为矩阵

的第一列即为修改之后链顶的答案矩阵的值(

值)。

,然后令

,意为令

跳到

所在重链链首的父亲。

再次线段树单点查询出

点所对应的转移矩阵

,令

考察

的贡献,为

那么矩阵

里的所有

变化量即为

。将

加上变化量即可。

考察

的贡献,为

那么矩阵

里的所有

变化量即为

。将

加上变化量即可。

矩阵

仍为

重复执行上述过程直至

时结束。

即可完成修改。

实现细节:

矩阵可以用

的二维数组存储。

矩阵乘可以循环展开。

线段树上非叶子节点存储的矩阵为左儿子矩阵

右儿子矩阵。

Code:

#include<bits/stdc++.h>

#define int long long

#define lp (p<<1)

#define rp ((p<<1)|1)

using namespace std;

inline int read()

{

int x=0,c=getchar(),f=0;

for(;c>'9'||c<'0';f=c=='-',c=getchar());

for(;c>='0'&&c<='9';c=getchar())

x=(x<<1)+(x<<3)+(c^48);

return f?-x:x;

}

inline void write(int x)

{

if(x<0) x=-x,putchar('-');

if(x>9) write(x/10);

putchar(x%10+'0');

}

#ifndef ONLINE_JUDGE

#define ONLINE_JUDGE

#endif

const int N=1e6+5;

int n,m;

int a[N];

int fa[N];

int tail[N];

int siz[N];

int son[N];

int dfn[N];

int tod[N];

int top[N];

int id[N];

int tot;

vector<int> E[N];

const int inf=1e12;

struct Martix

{

int a[2][2]={};

}I,t[N<<2];

Martix nw;

int f[N][2],g[N][2];

int tid[N<<2];

inline int max(int x,int y) { return x>y?x:y; }

Martix operator*(const Martix &x,const Martix &y)

{

Martix ans;

ans.a[0][0]=max(x.a[0][0]+y.a[0][0],x.a[0][1]+y.a[1][0]);

ans.a[0][1]=max(x.a[0][0]+y.a[0][1],x.a[0][1]+y.a[1][1]);

ans.a[1][0]=max(x.a[1][0]+y.a[0][0],x.a[1][1]+y.a[1][0]);

ans.a[1][1]=max(x.a[1][0]+y.a[0][1],x.a[1][1]+y.a[1][1]);

return ans;

}

Martix ksm(Martix x,int p)

{

Martix ans=I;

while(p)

{

if(p&1) ans=ans*x;

x=x*x;

p>>=1;

}

return ans;

}

void dfs1(int p,int f)

{

fa[p]=f;

siz[p]=1;

for(int to:E[p])

{

if(to==f) continue;

dfs1(to,p);

siz[p]+=siz[to];

if(siz[to]>siz[son[p]]) son[p]=to;

}

}

void dfs2(int p,int tp)

{

dfn[p]=++tot;

id[tot]=p;

top[p]=tp;

tail[tp]=p;

if(son[p]) dfs2(son[p],tp);

for(int to:E[p])

if(!dfn[to]) dfs2(to,to);

}

void dfs3(int p,int fa)

{

g[p][1]=a[p];

for(int to:E[p])

{

if(to==fa) continue;

dfs3(to,p);

if(to==son[p]) continue;

g[p][1]+=f[to][0];

g[p][0]+=max(f[to][0],f[to][1]);

}

f[p][0]=g[p][0]+max(f[son[p]][0],f[son[p]][1]);

f[p][1]=g[p][1]+f[son[p]][0];

}

void pushup(int p)

{

t[p]=t[lp]*t[rp];

}

void build(int l,int r,int p)

{

if(l==r)

{

tid[id[l]]=p;

t[p].a[0][0]=t[p].a[0][1]=g[id[l]][0];

t[p].a[1][0]=g[id[l]][1];

t[p].a[1][1]=-inf;

return;

}

int mid=(l+r)>>1;

build(l,mid,lp);

build(mid+1,r,rp);

pushup(p);

}

Martix query(int l,int r,int sl,int sr,int p)

{

if(p==0) exit(0);

if(sl<=l&&r<=sr) return t[p];

int mid=(l+r)>>1;

Martix ql=I,qr=I;

if(sl<=mid) ql=query(l,mid,sl,sr,lp);

if(sr>mid) qr=query(mid+1,r,sl,sr,rp);

return ql*qr;

}

void change(int l,int r,int x,int p,const Martix &nw)

{

if(l==r)

{

t[p]=nw;

return;

}

int mid=(l+r)>>1;

if(x<=mid) change(l,mid,x,lp,nw);

else change(mid+1,r,x,rp,nw);

pushup(p);

}

void change(int x,int y)

{

nw=t[tid[x]];

nw.a[1][0]+=y-a[x];

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/23 11:49:03

kubernetes终端管理神器

什么是 k9s K9s&#xff1a;提供了一个基于curses的终端UI来与您的 Kubernetes 集群 进行交互。该项目的目的是简化浏览&#xff0c;观察和管理应用程序的过程。K9s 持续监视 Kubernetes 的更改&#xff0c;并提供后续命令以与观察到的Kubernetes资源进行交互。 K9s 输出展示…

作者头像 李华
网站建设 2026/4/23 3:55:55

【MPC】模型预测控制(MPC)之多变量和状态空间研究附Matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;擅长数据处理、建模仿真、程序设计、完整代码获取、论文复现及科研仿真。&#x1f34e; 往期回顾关注个人主页&#xff1a;Matlab科研工作室&#x1f34a;个人信条&#xff1a;格物致知,完整Matlab代码及仿真咨询…

作者头像 李华
网站建设 2026/4/23 11:47:57

Java毕设项目:基于springboot的汽车租赁买卖管理系统的设计与实现(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/21 20:07:34

神经网络和深度学习 第四周:深度神经网络的关键概念

周为第一课的最后一周内容&#xff0c;就像标题一样&#xff0c;我们从第二周的逻辑回归&#xff0c;到第三周的浅层神经网络&#xff0c; 再到本周的深度神经网络的概念层层递进&#xff0c;第一课内容主要还是对神经网络框架的基本介绍&#xff0c;因此本周实际上的主要内容还…

作者头像 李华
网站建设 2026/4/21 0:47:21

【打造自己的 DeepSeek】第 1 期:为什么要打造自己的 DeepSeek? _

个人认为有两方面原因&#xff1a;一方面是 DeepSeek 使用方便。由于众所周知的原因&#xff0c;国内对国外网站的访问是有诸多限制的&#xff0c;其中就包括各大 AI 模型的官网。而 DeepSeek 是国内研发的&#xff0c;可以直接访问&#xff0c;网页使用是非常方便的。而且 Dee…

作者头像 李华