动态DP

何为动态DP?

将画风正常的DP加上修改操作。

举个例子?

给你一个长度为\(n\)的数列,从中选出一些数,要求选出的数互不相邻,最大化选出的数的和。

考虑DP,状态设计为\(f[i][1/0]\)表示考虑了前\(i\)个数,第\(i\)个数选/不选的最大和。

状态转移方程显然为:

\[f[i][0]=max(f[i-1][0],f[i-1][1])
\]

\[f[i][1]=f[i-1][0]+a[i]
\]

很简单对不对?

改成这样呢?

给你一个长度为\(n\)的数列。有\(m\)次操作,每次操作修改其中一个位置上的数或者从整个数列中选出一些数,要求选出的数不相邻,询问选出的数的和的最大值。

怎么做?

每次修改完重新暴力DP一遍?

不好意思,\(n,m \leq 100000\)。

那怎么办?

呦嚯,完蛋。

我们发现\(f[i-1][0/1]\)到\(f[i][0/1]\)的转移可以写成矩阵乘法的形式。

需要注意的是这里的矩阵乘法和一般的矩阵乘法略有不同,即用\(max\)替换原来的\(+\),用\(+\)代替原来的\(\times\)。

然后就可以使用线段树维护矩阵连乘,得到单次修改\(O(logn)\),单次询问\(O(1)\)的优秀算法了。

出在树上?

题目链接

题意

给定一棵\(n\)个点的树,点带点权。

有\(m\)次操作,每次操作给定

\(x,y\),表示修改点\(x\)的权值为\(y\)。

你需要在每次操作之后求出这棵树的最大权独立集的权值大小。

\(n,m \leq 100000\)。

暴力出奇迹?对于这道题来说不存在的。

题解

简而言之就是带修改树上最大权独立集。

首先,如果一个点的点权小于\(0\),我们可以认为其为\(0\),显然这样不会影响答案。(后来想想这步好像没啥用)

其实刚才那道例题是这道题的链的特殊情况。

但这也启发了我们要把树上的问题转化为序列上的问题,于是我们想到了树剖。

\(f[x][1/0]\)表示以\(x\)为根的子树中,结点\(x\)选/不选时的最大权独立集。\(g[x][1/0]\)表示以\(x\)为根的子树中,不考虑以\(heavychild[x]\)为根的子树,结点\(x\)选/不选时的最大权独立集。

这里的\(g\)数组就类似于上一题的\(a\)数组。

树剖后使用线段树维护每条重链上的矩阵连乘,转移矩阵与上题类似。

(\(ver\)代表\(heavychild[x]\))

(\(g\)的下标好像有点挤)

这样就可以实现带修改了。具体来说,就是在线段树上修改->跳重链->处理这个轻子树的影响->在线段树上修改->跳重链->处理这个轻子树的影响->...如此循环直到处理完\(top\)为\(1\)的重链为止。

时间复杂度\(O(nlog^2n)\)。

要注意矩阵乘法部分如果和我写的一样的话线段树上需要倒着乘(矩阵乘法无交换律)。

代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <cctype>
#include <algorithm>
#define rin(i,a,b) for(int i=(a);i<=(b);i++)
#define rec(i,a,b) for(int i=(a);i>=(b);i--)
#define trav(i,a) for(int i=head[(a)];i;i=e[i].nxt)
using std::cin;
using std::cout;
using std::endl;
typedef long long LL; inline LL read(){
LL x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return x*f;
} const int MAXN=100005;
int n,m;
int ecnt,head[MAXN];
LL w[MAXN];
int fa[MAXN],dep[MAXN],siz[MAXN],pc[MAXN],top[MAXN],id[MAXN],ed[MAXN],num[MAXN],tot;
LL f[MAXN][2];
int loc,ql,qr;
struct Edge{
int to,nxt;
}e[MAXN<<1];
struct Mat{
LL g[2][2];
Mat(){memset(g,0xc0,sizeof g);}
friend Mat operator * (Mat x,Mat y){
Mat ret;
rin(i,0,1) rin(j,0,1) rin(k,0,1)
ret.g[i][j]=std::max(ret.g[i][j],x.g[i][k]+y.g[k][j]);
return ret;
}
}tr[MAXN<<2],sav[MAXN]; inline void add_edge(int bg,int ed){
ecnt++;
e[ecnt].to=ed;
e[ecnt].nxt=head[bg];
head[bg]=ecnt;
} void dfs1(int x,int pre,int depth){
fa[x]=pre;
dep[x]=depth;
siz[x]=1;
int maxsiz=-1;
trav(i,x){
int ver=e[i].to;
if(ver==pre) continue;
dfs1(ver,x,depth+1);
siz[x]+=siz[ver];
if(siz[ver]>maxsiz){
maxsiz=siz[ver];
pc[x]=ver;
}
}
} void dfs2(int x,int topf){
top[x]=topf;
id[x]=++tot;
num[tot]=x;
if(!pc[x]) return;
dfs2(pc[x],topf);
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]||ver==pc[x]) continue;
dfs2(ver,ver);
}
} void dfs3(int x){
f[x][1]=w[x];
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]) continue;
dfs3(ver);
f[x][0]+=std::max(f[ver][0],f[ver][1]);
f[x][1]+=f[ver][0];
}
} #define mid ((l+r)>>1)
#define lc (o<<1)
#define rc ((o<<1)|1)
void build(int o,int l,int r){
if(l==r){
int x=num[l];
LL g0=0,g1=w[x];
trav(i,x){
int ver=e[i].to;
if(ver==fa[x]||ver==pc[x]) continue;
g0+=std::max(f[ver][0],f[ver][1]);
g1+=f[ver][0];
}
tr[o].g[0][0]=tr[o].g[1][0]=g0;
tr[o].g[0][1]=g1;
tr[o].g[1][1]=-1e18;
sav[l]=tr[o];
return;
}
build(lc,l,mid);
build(rc,mid+1,r);
tr[o]=tr[rc]*tr[lc];
} void upd(int o,int l,int r){
if(l==r){
tr[o]=sav[l];
return;
}
if(loc<=mid) upd(lc,l,mid);
else upd(rc,mid+1,r);
tr[o]=tr[rc]*tr[lc];
} Mat query(int o,int l,int r){
if(ql<=l&&r<=qr) return tr[o];
if(ql>mid) return query(rc,mid+1,r);
else if(qr<=mid) return query(lc,l,mid);
else return query(rc,mid+1,r)*query(lc,l,mid);
}
#undef mid
#undef lc
#undef rc inline Mat subquery(int x){
ql=id[x],qr=ed[x];
return query(1,1,n);
} inline void pathupd(int x,LL y){
if(w[x]==y) return;
LL temp=w[x];
w[x]=y;
bool flag=1;
Mat pre,now;
while(x){
if(flag){
sav[id[x]].g[0][1]+=y-temp;
pre=subquery(top[x]);
loc=id[x];
upd(1,1,n);
now=subquery(top[x]);
flag=0;
}
else{
sav[id[x]].g[0][0]+=std::max(now.g[0][0],now.g[0][1])
-std::max(pre.g[0][0],pre.g[0][1]);
sav[id[x]].g[1][0]=sav[id[x]].g[0][0];
sav[id[x]].g[0][1]+=now.g[0][0]-pre.g[0][0];
pre=subquery(top[x]);
loc=id[x];
upd(1,1,n);
now=subquery(top[x]);
}
x=fa[top[x]];
}
} int main(){
n=read(),m=read();
rin(i,1,n) w[i]=std::max(read(),0ll);
rin(i,2,n){
int u=read(),v=read();
add_edge(u,v);
add_edge(v,u);
}
dfs1(1,0,1);
dfs2(1,1);
dfs3(1);
rin(i,1,n)
ed[top[i]]=std::max(ed[top[i]],id[i]);
build(1,1,n);
while(m--){
int x=read();
LL y=std::max(read(),0ll);
pathupd(x,y);
Mat ans=subquery(1);
printf("%lld\n",std::max(ans.g[0][0],ans.g[0][1]));
}
return 0;
}

值得一提的是,可以使用\(Link-Cut\ Tree\)或全局平衡二叉树以达到更优的\(O(nlogn)\)的时间复杂度。

习题

[NOIP2018]保卫王国

动态DP总结的更多相关文章

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

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

  2. Luogu P4643 【模板】动态dp

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

  3. 动态dp学习笔记

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

  4. 洛谷P4719 动态dp

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

  5. 动态 DP 学习笔记

    不得不承认,去年提高组 D2T3 对动态 DP 起到了良好的普及效果. 动态 DP 主要用于解决一类问题.这类问题一般原本都是较为简单的树上 DP 问题,但是被套上了丧心病狂的修改点权的操作.举个例子 ...

  6. 动态dp初探

    动态dp初探 动态区间最大子段和问题 给出长度为\(n\)的序列和\(m\)次操作,每次修改一个元素的值或查询区间的最大字段和(SP1714 GSS3). 设\(f[i]\)为以下标\(i\)结尾的最 ...

  7. [总结] 动态DP学习笔记

    学习了一下动态DP 问题的来源: 给定一棵 \(n\) 个节点的树,点有点权,有 \(m\) 次修改单点点权的操作,回答每次操作之后的最大带权独立集大小. 首先一个显然的 \(O(nm)\) 的做法就 ...

  8. UOJ268 [清华集训2016] 数据交互 【动态DP】【堆】【树链剖分】【线段树】

    题目分析: 不难发现可以用动态DP做. 题目相当于是要我求一条路径,所有与路径有交的链的代价加入进去,要求代价最大. 我们把链的代价分成两个部分:一部分将代价加入$LCA$之中,用$g$数组保存:另一 ...

  9. [复习]动态dp

    [复习]动态dp 你还是可以认为我原来写的动态dp就是在扯蛋. [Luogu4719][模板]动态dp 首先作为一个\(dp\)题,我们显然可以每次修改之后都进行暴力\(dp\),设\(f[i][0/ ...

  10. 【BZOJ4911】[SDOI2017]切树游戏(动态dp,FWT)

    [BZOJ4911][SDOI2017]切树游戏(动态dp,FWT) 题面 BZOJ 洛谷 LOJ 题解 首先考虑如何暴力\(dp\),设\(f[i][S]\)表示当前以\(i\)节点为根节点,联通子 ...

随机推荐

  1. if——while表达式详解

    ①while循环的表达式是循环进行的条件,用作循环条件的表达式中一般至少包括一个能够改变表达式的变量,这个变量称为循环变量 ②当表达式的值为真(非零)(非空)时,执行循环体:为假(0)时,则循环结束 ...

  2. k8s--资源控制器

    资源控制器 1.什么是控制器 Kubernetes中内建了很多controller (控制器) ,这些相当于一个状态机,用来控制Pod的具体状态和行为 Pod 的分类 自主式 Pod:Pod 退出了, ...

  3. P1474货币系统

    这是USACO的一道DP题,难度是提高—. 这道题是告诉我们货币种类,问你用这些货币组成一个面值最大有多少种方案.第一眼看上去想用dfs记忆化,随后发现其实这个题很类似于完全背包,可以取无线件,但是他 ...

  4. 如何使用js在移动端和PC端居中

    在手机移动端和PC端控制居中是一个很蛋痛的问题,因为屏幕宽度在变化,所以就不要写死样式,那么我想用JS来控制,灵活的控制宽度,需要注意这三个时候: (1)首先需要在页面刚加载的时候就调用此函数, (2 ...

  5. django部署到linux上不显示.svg图标处理方法

    在setting文件的最开始添加如下内容: import mimetypes mimetypes.add_type("image/svg+xml", ".svg" ...

  6. vscode 热部署 spring-mvc

    1.添加maven插件 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId> ...

  7. linux下的系统服务管理及日志管理

    1.ntsysv服务配置工具 用来配置哪些服务开启或关闭,图形界面,使用键盘来操作. 安装ntsysv服务的命令:yum install -y ntsysv 直接运行命令ntsysv 弹出配置界面: ...

  8. Linux vim程序编辑器

    Tips: 在 vi 里面, [tab] 这个按钮所得到的结果与空格符所得到的结果是不一样的,特别强调一下! 一般模式 移动光标 30↓ 向下移动30行 40→ 向右移动40个字符 gg 移动到档案第 ...

  9. [转载]Quartus ii 一些Warning/Eeror分析与解决

    我会在此基础上继续添加 原文地址:ii 一些Warning/Eeror分析与解决">Quartus ii 一些Warning/Eeror分析与解决作者:yanppf 注:http:// ...

  10. Linux下载:wget、yum与apt-get用法及区别

    一般来说著名的linux系统基本上分两大类: RedHat系列:Redhat.Centos.Fedora等 Debian系列:Debian.Ubuntu等 RedHat 系列 常见的安装包格式 rpm ...