news 2026/7/1 21:40:33

【模板】动态 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/7/1 22:09:43

计算机Java毕设实战-基于springboot的汽车租赁买卖管理系统的设计与实现入库录入、租赁登记、租赁状态查询【完整源码+LW+部署说明+演示视频,全bao一条龙等】

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

作者头像 李华
网站建设 2026/6/29 5:10:09

华为OD机考双机位C卷 - 编程能力提升计划 (Java Python JS C/C++ GO )

最新华为上机考试 真题目录:点击查看目录 华为OD面试真题精选:点击立即查看 华为OD机考双机位C卷 题目描述 为了提升软件编码能力,小王制定了刷题计划,他选了题库中的n道题,编号从0到n-1,并计划在m天内按照题目编号顺序刷完所有的题目(注意,小王不能用多天完成同一…

作者头像 李华
网站建设 2026/6/30 22:24:45

ORACLE检查并创建表空间和表分区

为确保系统在高并发、大数据量环境下的稳定高效运行&#xff0c;要求建立完善的表空间与表分区管理机制&#xff0c;具体包括&#xff1a;定期检查表空间使用率&#xff0c;及时发现并处理空间不足风险&#xff1b;建立分区自动创建与维护流程&#xff0c;防止因分区缺失导致的…

作者头像 李华
网站建设 2026/7/1 4:40:57

港媒盛赞“香港媳妇”徐冬冬!婚照惊艳全网,港圈作品圈粉无数

12月18日&#xff0c;徐冬冬与尹子维的婚纱照强势空降热搜&#xff0c;甜酷兼具的造型让网友直呼美貌惊艳&#xff0c;气质独一份。从戏里媚骨天成的“大嫂”到戏外被港媒追捧的“香港媳妇”&#xff0c;这位东北大妞不仅用八年分合的爱情故事打动人心&#xff0c;更在港娱圈深…

作者头像 李华
网站建设 2026/6/30 2:01:50

Redis高级特性与生产环境部署

Redis高级特性与生产环境部署实践一、Redis核心数据类型深度解析1.1 哈希&#xff08;Hash&#xff09;类型详解1.1.1 哈希数据结构# 哈希结构示意图 key: "user:1001" value: {"name": "张三","age": 25,"city": "北京…

作者头像 李华
网站建设 2026/7/1 9:25:44

java计算机毕业设计网咖会员管理系统 电竞馆会员计费与点餐一体化平台 网吧会员上机充值及订单管理系统

计算机毕业设计网咖会员管理系统67kvh9&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。疫情后电竞消费井喷&#xff0c;传统网吧前台手工登记、纸质充值券、Excel对账的模式已无法…

作者头像 李华