动态DP(DDP)
动态DP是树上的、带修改的DP。修改操作一般而言用树剖加线段树加广义矩阵乘法来维护,复杂度可以达到 \(n\log^2 n\)。
叫DDP是不知从哪里延续下来的一种神秘简称。
P4719 【模板】动态 DP
给定一颗树,每个点有权值,维护最大独立集。最大独立集指没有两个集合中的点被一条边直接相连。
如果不带修,那就是一道很简单的 DP 题。
设 \(f_{u,0/1}\) 表示某个点选或者不选,它自己和其子树中选的点的最大权值。
方程也是比较直观的:
\left\{\begin{matrix}
f_{u,0} =\sum _v \max (f_{v,0},f_{v,1}) \\
f_{u,1} =\sum _v f_{v,0}
\end{matrix}\right.
\end{aligned}
\]
现在考虑带修改怎么做。
显然树剖是必要的。然后问题就变成如何快速的维护轻重链间的答案,同时一条重链内的答案可以被线段树合并维护。
考虑这么一个另外的变量 \(g_{u,0/1}\) 表示对于某一个点,如果不考虑其重儿子,这个点选或者不选,这棵子树的最大权值。
这样我们就可以将轻重儿子间的信息分开单独维护。新的通过 \(g\) 来转移的方程式:
\left\{\begin{matrix}
f_{u,0} =\max (g_{u,0}+f_{son_u,1},g_{u,0}+f_{son_u,0}) \\
f_{u,1} = g_{u,1}+f_{son_u,0}
\end{matrix}\right.
\end{aligned}
\]
这里 \(son_u\) 表示 \(u\) 的重儿子。
看起来好像没什么大用,反而还要多维护一个信息。但是这恰恰是线段树可以快速合并重链信息的关键。
考虑这种转移方程可以转化为一种广义矩阵乘法的形式。
具体的,设矩阵间的“*”积表示:
\]
这时方程就可以写为:
g_{u,0} & g_{u,0}\\
g_{u,1} & -\infty
\end{vmatrix}*
\begin{vmatrix}
f_{son_u,0} \\
f_{son_u,1}
\end{vmatrix}=
\begin{vmatrix}
f_{u,0} \\
f_{u,1}
\end{vmatrix}
\]
如果我们知道了一条重链上的所有的点的 \(g\) 数组和重链底的那个点的 \(f\),那就可以通过线段树合并出重链顶的 \(f\)。
由于我们只会查询树根的 \(f\) 值,而树根一定是一个重链顶。
也就是现在只需要用线段树维护前面只与 \(g\) 有关的转移矩阵,将其合并起来就做完了。
首先我们考虑单点修改的时候会造成什么影响。修改首先会影响当前重链的答案,然后传到更上面的一条重链,一直传递下去。
假设当前传递到的这条重链的顶为 \(u\) ,其父亲也就是更上面的那一条重链的底是 \(v\)。显然的,\(u\) 一定是 \(v\) 的轻儿子。那就说明 \(u\) 的 \(f\) 值只会影响到 \(v\) 的 \(g\) 值。
由于 \(g_{v,0}\) 的计算是所有轻儿子的 \(\sum \max{f_{u,0},f_{u,1}}\),因此只需要将修改之前的 \(\max{f_{u,0},f_{u,1}}\) 减掉,将新的加回来就可以了。\(g_{v,1}\) 同理。
由于按照矩阵的转移式,我们前面一堆转移矩阵后面还要跟一个 \(f\) 的矩阵。那这需要特判吗?
这里有一个比较巧妙的设计,可以发现在矩阵中 \(g_{u,0},g{u,1}\) 与 \(f_{u,0},f_{u,1}\) 的位置是对应的。也就是说对于没有重儿子的叶子节点,我们也可以将本应是 \(f\) 的值直接塞到转移矩阵的第一列对应位置。这样矩乘的时候自然也就将其包含,放在最后面“*”乘起来了。
code
仿照了一下题解的写法。但是比题解“略有”压行。
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+7,inf=1e9+7;
int n,m,a[N],tot[N],dep[N],siz[N],son[N],dfn[N],loc[N],top[N],dfncnt=0,len[N],f[N][2],fat[N];
vector <int> q[N];
struct node{int mp[2][2];};
void init2(node &x){for(int i=0;i<2;i++)for(int j=0;j<2;j++) x.mp[i][j]=0;}void init3(node &x){for(int i=0;i<2;i++)for(int j=0;j<2;j++) x.mp[i][j]=-inf;}
node operator * (const node &x,const node &y){
node res;init3(res);
for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++) res.mp[i][j]=max(res.mp[i][j],x.mp[i][k]+y.mp[k][j]);
return res;
}
node tra[N],tr[N];
void dfs1(int u,int fa){
dep[u]=dep[fa]+1,siz[u]=1;fat[u]=fa;
for(int i=0;i<tot[u];i++){
int v=q[u][i];if(v==fa) continue;
dfs1(v,u);son[u]=siz[son[u]]<siz[v]?v:son[u];siz[u]+=siz[v];
}
}
void dfs2(int u,int fa,int t){
dfn[u]=++dfncnt,loc[dfncnt]=u;top[u]=t,len[t]++;
init2(tra[u]);f[u][0]=0,f[u][1]=tra[u].mp[1][0]=a[u];tra[u].mp[1][1]=-inf;
if(son[u]) dfs2(son[u],u,t),f[u][0]+=max(f[son[u]][0],f[son[u]][1]),f[u][1]+=f[son[u]][0];
for(int i=0;i<tot[u];i++){
int v=q[u][i];if(v==fa||v==son[u]) continue;
dfs2(v,u,v);int tmp1=max(f[v][0],f[v][1]),tmp2=f[v][0];
f[u][0]+=tmp1,f[u][1]+=tmp2;
tra[u].mp[0][0]+=tmp1,tra[u].mp[1][0]+=tmp2;
}
tra[u].mp[0][1]=tra[u].mp[0][0];
}
#define ls (u<<1)
#define rs (u<<1|1)
void push_up(int u){tr[u]=tr[ls]*tr[rs];}
void build(int u,int l,int r){
if(l==r) {tr[u]=tra[loc[l]];return;}
int mid=(l+r)>>1;build(ls,l,mid),build(rs,mid+1,r);
push_up(u);
}
void modify(int u,int l,int r,int x){
if(l==r) {tr[u]=tra[loc[l]];return;}
int mid=(l+r)>>1;if(x<=mid)modify(ls,l,mid,x);else modify(rs,mid+1,r,x);
push_up(u);
}
node query(int u,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){return tr[u];}
int mid=(l+r)>>1;
if(qr<=mid) return query(ls,l,mid,ql,qr);if(ql>mid) return query(rs,mid+1,r,ql,qr);
return query(ls,l,mid,ql,qr)*query(rs,mid+1,r,ql,qr);
}
void update(int u,int val){
tra[u].mp[1][0]+=val-a[u],a[u]=val;
node x,y;
while(u){
x=query(1,1,n,dfn[top[u]],dfn[top[u]]+len[top[u]]-1);modify(1,1,n,dfn[u]);y=query(1,1,n,dfn[top[u]],dfn[top[u]]+len[top[u]]-1);
u=fat[top[u]];
tra[u].mp[0][0]+=max(y.mp[0][0],y.mp[1][0])-max(x.mp[0][0],x.mp[1][0]);
tra[u].mp[1][0]+=y.mp[0][0]-x.mp[0][0];tra[u].mp[0][1]=tra[u].mp[0][0];
}
}
signed main(){
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
cin>>n>>m;for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,u,v;i<=n-1;i++) cin>>u>>v,q[u].push_back(v),tot[u]++,q[v].push_back(u),tot[v]++;
dfs1(1,0),dfs2(1,0,1);build(1,1,n);
for(int i=1,u,x;i<=m;i++){
cin>>u>>x;update(u,x);node res=query(1,1,n,dfn[top[1]],dfn[top[1]]+len[top[1]]-1);
cout<<max(res.mp[0][0],res.mp[1][0])<<'\n';
}
return 0;
}
动态DP(DDP)的更多相关文章
- 洛谷P4719 【模板】动态dp(ddp LCT)
题意 题目链接 Sol 动态dp板子题.有些细节还没搞懂,待我研究明白后再补题解... #include<bits/stdc++.h> #define LL long long using ...
- 动态DP,ddp
动态DP?动态动态规划? 个人理解:动态DP,就是普通DP加修改操作,然后就变成了个毒瘤题. 直接就着例题写吧. 例题 P4719 [模板]"动态 DP"&动态树分治 求树 ...
- 洛谷4719 【模板】动态dp 学习笔记(ddp 动态dp)
qwq大概是混乱的一个题. 首先,还是从一个比较基础的想法开始想起. 如果每次暴力修改的话,那么每次就可以暴力树形dp 令\(dp[x][0/1]\)表示\(x\)的子树中,是否选择\(x\)这个点的 ...
- 「校内训练 2019-04-23」越野赛车问题 动态dp+树的直径
题目传送门 http://192.168.21.187/problem/1236 http://47.100.137.146/problem/1236 题解 题目中要求的显然是那个状态下的直径嘛. 所 ...
- 【学习笔记】动态 dp 入门简易教程
序列 dp 引入:最大子段和 给定一个数列 \(a_1, a_2, \cdots, a_n\)(可能为负),求 \(\max\limits_{1\le l\le r\le n}\left\{\sum_ ...
- Note -「动态 DP」学习笔记
目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...
- 动态DP之全局平衡二叉树
目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...
- Luogu P4643 【模板】动态dp
题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...
- 动态dp学习笔记
我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...
- 洛谷P4719 动态dp
动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ...
随机推荐
- 使用 Git 命令和 Github 前须了解的知识
本文不包括 Git 命令的介绍与使用,只分享 Git 的关键概念与 Github 项目的基本工作流程.作者相信先了解它们对后续的学习和工作大有裨益.(如有错误和建议请大家评论告知) 版本控制系统 VC ...
- 让Typecho支持Emoji表情,解决报错:Database Query Error
最近在使用一个主题时,看到搭配emoji表情可以让改主题更加美观,于是我就上了,结果在将emoji表情放进去保存的时候报错:Database Query Error,于是问起了度娘.最后的结果是: 在 ...
- Linux - 搭建一套Apache大数据集群
一.服务器操作系统 主机名 操作系统 node01 Centos 7.9 node02 Centos 7.9 node03 Centot 7.9 二.大数据服务版本 服务 版本 下载 JDK jdk- ...
- Spark - spark on yarn 的作业提交流程
YarnClient YarnCluster 客户端(Client)通过YARN的ResourceManager提交应用程序.在此过程中,客户端进行权限验证,生成Job ID和资源上传路径,并将这些信 ...
- Processing多窗口程序范例(二)
多窗口范例(二),做一个划线生成图像的应用,最后结果: 子窗口划线,主窗口复制多个画布叠加并添加了旋转动画. 范例程序 主程序: package syf.demo.multiwindow2; impo ...
- LangChain大模型框架& Dify低代码 AI 开发平台
目录 1. LangChain介绍 1.1 架构 1.2 概念 1.3 术语 1.4 LangChain实战 2. LLM 应用开发平台dify 2.1 dify安装 2.2 设置知识库 3. dif ...
- 如果服务器是 PHP,并且 GET 请求可以接收到数据,但 POST 请求接收不到数据,可能是以下原因之一
如果服务器是 PHP,并且 GET 请求可以接收到数据,但 POST 请求接收不到数据,可能是以下原因之一: PHP 未正确解析 POST 请求体:PHP 需要通过 $_POST 或 php://in ...
- 内网环境部署Deepseek+Dify,构建企业私有化AI应用
0.简介 公司为生产安全和保密,内部的服务器不可连接外部网络,为了可以在内网环境下部署,采用的方案为ollama(Docker)+Dify(Docker Compose),方便内网环境下迁移和备份,下 ...
- 【记录】C/C++-关于I/O的坑与教训
吐槽 每每读取字符串时,倘若稍有灵活的操作,总会遇上诡异奇怪的事情.究其原因,就是没完全理解一些基本读写函数的机制.这次做Uva227就把I/O上的问题全暴露出来了.想来还是应该记录一些经验教训. 记 ...
- 使用Istio灰度发布
目录 灰度发布 1. Istio 1.1 Istio介绍 1.2 Istio是如何工作的 2. 安装Istio 2.1 环境 2.2 得到二进制文件 2.3 安装istio 3. 部署bookinfo ...