news 2026/4/15 8:33:12

【模板】动态 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/15 2:58:57

[python] 代码性能分析工具line_profiler使用指北

码分析能够评估各部分代码的时间消耗&#xff0c;即进行时间复杂度分析。通过这一过程&#xff0c;我们可以识别影响整体运行效率的关键部分&#xff0c;从而更高效地利用底层计算资源。此外&#xff0c;代码分析也可用于评估内存使用情况&#xff0c;即空间复杂度&#xff0c;…

作者头像 李华
网站建设 2026/4/11 12:07:05

《手搓》线程池

一、什么是《手搓》线程池手搓线程池并不是用来完全代替系统线程池的你可以把手搓线程池看做系统线程池的一部分就好比在东海用集装箱搞养殖一个集装箱里养鱼另一个集装箱里养虾搞好隔离,鱼虾都不耽搁二、最常用线程池的场景是什么当然是Task,是用TaskFactory.StartNew方法创建…

作者头像 李华
网站建设 2026/4/12 3:37:09

【MUSIC、最大似然与克拉美-罗下界】MUSIC与ESPRIT 算法来估计到达角(AoA),并尝试推导克拉美-罗下界(CRLB)以分析其性能研究附Matlab代码

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

作者头像 李华
网站建设 2026/4/8 1:14:18

LLM 场景下的强化学习技术扫盲

强化学习基础&#xff1a;行业黑话想象你正在和一个刚训练好的语言模型聊天。你问&#xff1a;“今天过得怎么样&#xff1f;”模型可能回&#xff1a;“还行。” 也可能回&#xff1a;“我是个 AI&#xff0c;没有感情。”人类觉得前者更自然、更友好——这就是偏好反馈。强化…

作者头像 李华
网站建设 2026/4/14 3:25:57

多智能体协同系统

多智能体协同系统的核心概念 多智能体协同系统&#xff08;Multi-Agent Systems, MAS&#xff09;通过多个自主智能体的交互实现复杂任务&#xff0c;广泛应用于机器人协作、自动驾驶、游戏AI等领域。核心特性包括分布式决策、通信协议、任务分配与冲突解决。典型应用案例 1. 无…

作者头像 李华