当前位置: 首页 > news >正文

【模板】动态 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];

http://www.cnnetsun.cn/news/174850.html

相关文章:

  • 一键配置 Web 前端开发环境(PowerShell 自动化脚本)
  • 程序员必备技能:AI Agent 9种设计模式深度解析,提升大模型应用效能(值得收藏)
  • 【python大数据毕设实战】哮喘患者症状数据可视化分析系统、Hadoop、计算机毕业设计、包括数据爬取、数据分析、数据可视化、机器学习
  • 9 个降AI率工具,MBA 必备避坑指南
  • Windows系统文件inetmib1.dll丢失损坏 下载修复方法
  • Boost电路的右半平面零点
  • 【全球AI伦理治理】
  • 毕业季必看!7款免费AI写论文神器实测,一站式搞定选题、大纲到降重
  • LLMs之Survey之Agent:《Measuring Agents in Production》翻译与解读
  • 零代码上手Google Gemini 3:5种实用方法大揭秘
  • “你用的那个AI,到底把你坑了还是救了?”——解锁宏智树论文的协作新范式
  • 好写作AI:别等学校采购了!你的论文“救命神器”自己就能用上
  • Windows系统文件GdiPlus.dll丢失或损坏 下载修复方法
  • 研究生必备8款AI写论文神器:5分钟生成25000字问卷类论文,自动生成高信度数据
  • 【BuildFlow 筑流】unitrix_macros库 Cargo.toml 配置详解及依赖库用法
  • 《开发者出海必看:如何优雅地搞定海外服务支付?(保姆级干货)》
  • Thinkphp和Laravel企业防爆安全设备信息系统
  • Thinkphp和Laravel全家桶鲜花售卖商城系统vue
  • 记录我适配iOS26遇到的一些问题
  • 通过命令模拟pod创建
  • 同步机无感 STM32 低成本 MD500E 永磁同步控制方案大揭秘
  • 小宝玩具 【通达信、源码 、主图、附图】
  • 使用 Github Pages 和 Hexo
  • 审稿 一区期刊注意事项: journal offers the option to connec;please note, reviewers are not expected 是什么意思
  • 线性代数:多维世界的变形工具箱
  • 力扣题目142. 环形链表 II​的解法分享,附图解
  • MATLAB电力系统继电保护之自动重合闸
  • 10 个AI写作工具,助你轻松搞定继续教育论文!
  • 【开题答辩全过程】以 基于Vue的茶道知识科普网站的设计与实现为例,包含答辩的问题和答案
  • 主动配电网两阶段鲁棒恢复:Matlab 代码探索之旅