动态DP是树上的、带修改的DP。修改操作一般而言用树剖加线段树加广义矩阵乘法来维护,复杂度可以达到 \(n\log^2 n\)。

叫DDP是不知从哪里延续下来的一种神秘简称。

P4719 【模板】动态 DP

给定一颗树,每个点有权值,维护最大独立集。最大独立集指没有两个集合中的点被一条边直接相连。

如果不带修,那就是一道很简单的 DP 题。

设 \(f_{u,0/1}\) 表示某个点选或者不选,它自己和其子树中选的点的最大权值。

方程也是比较直观的:

\[\begin{aligned}
\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\) 来转移的方程式:

\[\begin{aligned}
\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\) 的重儿子。

看起来好像没什么大用,反而还要多维护一个信息。但是这恰恰是线段树可以快速合并重链信息的关键。

考虑这种转移方程可以转化为一种广义矩阵乘法的形式。

具体的,设矩阵间的“*”积表示:

\[a*b=c \Leftrightarrow c_{i,j}=\max_k(a_{i,k}+b_{k,j})
\]

这时方程就可以写为:

\[\begin{vmatrix}
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)的更多相关文章

  1. 洛谷P4719 【模板】动态dp(ddp LCT)

    题意 题目链接 Sol 动态dp板子题.有些细节还没搞懂,待我研究明白后再补题解... #include<bits/stdc++.h> #define LL long long using ...

  2. 动态DP,ddp

    动态DP?动态动态规划? 个人理解:动态DP,就是普通DP加修改操作,然后就变成了个毒瘤题. 直接就着例题写吧. 例题 P4719 [模板]"动态 DP"&动态树分治 求树 ...

  3. 洛谷4719 【模板】动态dp 学习笔记(ddp 动态dp)

    qwq大概是混乱的一个题. 首先,还是从一个比较基础的想法开始想起. 如果每次暴力修改的话,那么每次就可以暴力树形dp 令\(dp[x][0/1]\)表示\(x\)的子树中,是否选择\(x\)这个点的 ...

  4. 「校内训练 2019-04-23」越野赛车问题 动态dp+树的直径

    题目传送门 http://192.168.21.187/problem/1236 http://47.100.137.146/problem/1236 题解 题目中要求的显然是那个状态下的直径嘛. 所 ...

  5. 【学习笔记】动态 dp 入门简易教程

    序列 dp 引入:最大子段和 给定一个数列 \(a_1, a_2, \cdots, a_n\)(可能为负),求 \(\max\limits_{1\le l\le r\le n}\left\{\sum_ ...

  6. Note -「动态 DP」学习笔记

    目录 「CF 750E」New Year and Old Subsequence 「洛谷 P4719」「模板」"动态 DP" & 动态树分治 「洛谷 P6021」洪水 「S ...

  7. 动态DP之全局平衡二叉树

    目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...

  8. Luogu P4643 【模板】动态dp

    题目链接 Luogu P4643 题解 猫锟在WC2018讲的黑科技--动态DP,就是一个画风正常的DP问题再加上一个动态修改操作,就像这道题一样.(这道题也是PPT中的例题) 动态DP的一个套路是把 ...

  9. 动态dp学习笔记

    我们经常会遇到一些问题,是一些dp的模型,但是加上了什么待修改强制在线之类的,十分毒瘤,如果能有一个模式化的东西解决这类问题就会非常好. 给定一棵n个点的树,点带点权. 有m次操作,每次操作给定x,y ...

  10. 洛谷P4719 动态dp

    动态DP其实挺简单一个东西. 把DP值的定义改成去掉重儿子之后的DP值. 重链上的答案就用线段树/lct维护,维护子段/矩阵都可以.其实本质上差不多... 修改的时候在log个线段树上修改.轻儿子所在 ...

随机推荐

  1. 使用 Git 命令和 Github 前须了解的知识

    本文不包括 Git 命令的介绍与使用,只分享 Git 的关键概念与 Github 项目的基本工作流程.作者相信先了解它们对后续的学习和工作大有裨益.(如有错误和建议请大家评论告知) 版本控制系统 VC ...

  2. 让Typecho支持Emoji表情,解决报错:Database Query Error

    最近在使用一个主题时,看到搭配emoji表情可以让改主题更加美观,于是我就上了,结果在将emoji表情放进去保存的时候报错:Database Query Error,于是问起了度娘.最后的结果是: 在 ...

  3. Linux - 搭建一套Apache大数据集群

    一.服务器操作系统 主机名 操作系统 node01 Centos 7.9 node02 Centos 7.9 node03 Centot 7.9 二.大数据服务版本 服务 版本 下载 JDK jdk- ...

  4. Spark - spark on yarn 的作业提交流程

    YarnClient YarnCluster 客户端(Client)通过YARN的ResourceManager提交应用程序.在此过程中,客户端进行权限验证,生成Job ID和资源上传路径,并将这些信 ...

  5. Processing多窗口程序范例(二)

    多窗口范例(二),做一个划线生成图像的应用,最后结果: 子窗口划线,主窗口复制多个画布叠加并添加了旋转动画. 范例程序 主程序: package syf.demo.multiwindow2; impo ...

  6. LangChain大模型框架& Dify低代码 AI 开发平台

    目录 1. LangChain介绍 1.1 架构 1.2 概念 1.3 术语 1.4 LangChain实战 2. LLM 应用开发平台dify 2.1 dify安装 2.2 设置知识库 3. dif ...

  7. 如果服务器是 PHP,并且 GET 请求可以接收到数据,但 POST 请求接收不到数据,可能是以下原因之一

    如果服务器是 PHP,并且 GET 请求可以接收到数据,但 POST 请求接收不到数据,可能是以下原因之一: PHP 未正确解析 POST 请求体:PHP 需要通过 $_POST 或 php://in ...

  8. 内网环境部署Deepseek+Dify,构建企业私有化AI应用

    0.简介 公司为生产安全和保密,内部的服务器不可连接外部网络,为了可以在内网环境下部署,采用的方案为ollama(Docker)+Dify(Docker Compose),方便内网环境下迁移和备份,下 ...

  9. 【记录】C/C++-关于I/O的坑与教训

    吐槽 每每读取字符串时,倘若稍有灵活的操作,总会遇上诡异奇怪的事情.究其原因,就是没完全理解一些基本读写函数的机制.这次做Uva227就把I/O上的问题全暴露出来了.想来还是应该记录一些经验教训. 记 ...

  10. 使用Istio灰度发布

    目录 灰度发布 1. Istio 1.1 Istio介绍 1.2 Istio是如何工作的 2. 安装Istio 2.1 环境 2.2 得到二进制文件 2.3 安装istio 3. 部署bookinfo ...