【总结】动态dp

动态DP

动态DP详解

Luogu P4643 【模板】动态dp

Part 1. 序列动态DP

题目描述

一段区间的价值的定义如下:
可在区间内取任意个数, 这些数位置不能重复 且不能相邻.
其和的最大值为这段区间的价值.
如有序列(1,-1,-2,3,4,2,-1),则区间[4,6]的价值为5。

给定一数列, 要求支持下列两种操作:

  1. 单点修改
  2. 查询给定区间价值

对于 100 % 100\% 100% 的数据, n , m ≤ 1 0 5 n,m\le 10^5 n,m105.

solution:

序列dp+单点修改。

f i , 0 f_{i,0} fi,0 表示不选第 i i i 位时最大和, f i , 1 f_{i,1} fi,1 表示选第 i i i 位时最大和

则有状态转移方程:

f i , 0 = m a x ( f i − 1 , 0 , f i − 1 , 1 ) f_{i,0}=max(f_{i-1,0},f_{i-1,1}) fi,0=max(fi1,0,fi1,1)

f i , 1 = f i − 1 , 0 + a i f_{i,1}=f_{i-1,0}+a_i fi,1=fi1,0+ai

单次查询复杂度 O ( n ) O(n) O(n)

修改操作直接 O ( 1 ) O(1) O(1) 修改 a i a_i ai 的值。

m m m 次操作,算法总复杂度 O ( n m ) O(nm) O(nm)

这个转移写成矩阵就是:

[ f i − 1 , 0 f i − 1 , 1 ] × [ 0 0 a i − ∞ ] = [ f i , 0 f i , 1 ] \left[ \begin{matrix} f_{i-1,0} \\ f_{i-1,1} \end{matrix} \right] × \left[ \begin{matrix} 0 & 0 \\ a_i & -\infty \end{matrix} \right] =\left[ \begin{matrix} f_{i,0} \\ f_{i,1} \end{matrix} \right] [fi1,0fi1,1]×[0ai0]=[fi,0fi,1]

我们把 × 变成了 + ,+ 变成了 max。

A i = [ 0 0 a i − ∞ ] A_i=\left[ \begin{matrix} 0 & 0 \\ a_i & -\infty \end{matrix} \right] Ai=[0ai0]

初始值 f L − 1 , 0 = f L − 1 , 1 = 0 f_{L-1,0}=f_{L-1,1}=0 fL1,0=fL1,1=0

[ L , R ] [L,R] [L,R] 的价值 = [ 0 0 ] \left[ \begin{matrix} 0 \\ 0 \end{matrix} \right] [00] × ∏ i = L R A i \prod_{i=L}^RA_i i=LRAi

若求得区间矩阵乘积,即可直接求得区间价值。

区间矩阵乘积,可用线段树维护。

#include<bits/stdc++.h> using namespace std; const int mxn=1e5+10; const int INF=0x3f3f3f3f; inline int read() { int X=0; bool flag=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();} while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();} if(flag) return X; return ~(X-1); } struct Matrix { int c[2][2]; Matrix() {memset(c,0,sizeof(c));} Matrix operator *(const Matrix &a) { Matrix r; for(int i=0;i<2;i++) for(int j=0;j<2;j++) { r.c[i][j]=-INF; for(int k=0;k<2;k++) r.c[i][j]=max(r.c[i][j],c[i][k]+a.c[k][j]); } return r; } }; struct SegmentTree { Matrix sum; }t[mxn<<2]; int n,m,val[mxn]; void up(int p) { t[p].sum=t[p*2].sum*t[p*2+1].sum; } void Build(int p,int l,int r) { if(l==r) { t[p].sum.c[1][0]=val[l]; t[p].sum.c[1][1]=-INF; return; } int mid=(l+r)>>1; Build(p*2,l,mid),Build(p*2+1,mid+1,r); up(p); } void Modify(int p,int l,int r,int x,int y) { if(l==r) { t[p].sum.c[1][0]=y; return; } int mid=(l+r)>>1; if(x<=mid) Modify(p*2,l,mid,x,y); else Modify(p*2+1,mid+1,r,x,y); up(p); } Matrix Query(int p,int l,int r,int ql,int qr) { if(ql<=l&&r<=qr) return t[p].sum; int mid=(l+r)>>1; if(qr<=mid) return Query(p*2,l,mid,ql,qr); if(ql>mid) return Query(p*2+1,mid+1,r,ql,qr); return Query(p*2,l,mid,ql,qr)*Query(p*2+1,mid+1,r,ql,qr); } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) val[i]=read(); Build(1,1,n); Matrix ans; for(int i=1;i<=m;i++) { int opt=read(),x=read(),y=read(); if(opt==1) Modify(1,1,n,x,y); else { Matrix ans; ans=ans*Query(1,1,n,x,y); printf("%d\n",max(ans.c[0][0],ans.c[0][1])); } } }

Part 2. 树上动态DP

给定一个长度为 n n n 的序列,你需要维护两种操作。

①查询一个区间的最大子段和;
②单点修改(即将一个位置上的数改成另一个数)

n , q ≤ 1 0 5 n , q ≤ 10^5 n,q105

solution:

定义 A i = [ 0 0 V i − ∞ ] A_i=\left[ \begin{matrix} 0 & 0 \\ V_i & -\infty \end{matrix} \right] Ai=[0Vi0]

不难把状态转移方程写成这个样子:
[ f v , 0 f v , 1 ] × [ f i , 0 ′ f i , 0 ′ f i , 1 ′ − ∞ ] = [ f i , 0 f i , 1 ] \left[\begin{matrix}f_{v,0} \\ f_{v,1} \end{matrix}\right]×\left[\begin{matrix}f^{'}_{i,0} & f^{'}_{i,0} \\ f^{'}_{i,1} & -\infty\end{matrix}\right]=\left[\begin{matrix}f_{i,0} \\ f_{i,1}\end{matrix}\right] [fv,0fv,1]×[fi,0fi,1fi,0]=[fi,0fi,1]
对于链的情况,可以写成这个形式:

[ f x , 0 f x , 1 ] \left[ \begin{matrix} f_{x,0} \\ f_{x,1} \end{matrix} \right] [fx,0fx,1] = ∏ i ∈ s u b s t r e e x [ f i , 0 f i , 0 f i , 1 − ∞ ] \prod_{i\in substree_x}\left[\begin{matrix}f_{i,0} & f_{i,0} \\ f_{i,1} & -\infty\end{matrix}\right] isubstreex[fi,0fi,1fi,0]

线段树可以维护上面那样序列上的 d p dp dp 转移,注意序列上的 d p dp dp 值是可以通过一个原始值乘上若干转移矩阵得到的。

拿到 x x x f v , 0 , f v , 1 {f_{v,0},f_{v,1}} fv,0,fv,1 后,要把它转化成标准形式。

线段树已经完成了区间合并操作,可以很快得到一个单点的值。

注意这里是单点修改,只对一个儿子产生影响,所以可以在原dp值上进行转移修改。

注意 f v , 0 , f v , 1 {f_{v,0},f_{v,1}} fv,0,fv,1 不包括重儿子,重儿子在线段树上查询即可。

v a l v , 0 , v a l v , 1 val_{v,0},val_{v,1} valv,0,valv,1 f v , 0 , f v , 1 f_{v,0},f_{v,1} fv,0,fv,1 必须单独处理,后者是不含 s o n x son_x sonx 的前者转移而来!

#include<bits/stdc++.h> #define int long long using namespace std; const int mx=1e5+5; const int INF=100000000; inline int read() { int X=0; bool flag=1; char ch=getchar(); while(ch<'0'||ch>'9') {if(ch=='-') flag=0; ch=getchar();} while(ch>='0'&&ch<='9') {X=(X<<1)+(X<<3)+ch-'0'; ch=getchar();} if(flag) return X; return ~(X-1); } struct matrix{ int c[2][2]; matrix() {memset(c,0,sizeof(c));} matrix operator *(const matrix &a) { matrix r; for(int i=0;i<2;i++) for(int j=0;j<2;j++) { r.c[i][j]=-INF; for(int k=0;k<2;k++) { r.c[i][j]=max(r.c[i][j],c[i][k]+a.c[k][j]); } } return r; } }val[mx],t[mx<<2]; int n,m; int head[mx*2],to[mx*2],nxt[mx*2],cnt; int a[mx],tp[mx],ed[mx],son[mx],fa[mx],dfn[mx],siz[mx],rnk[mx],num; int f[mx][2]; void add(int x,int y) {to[++cnt]=y,nxt[cnt]=head[x],head[x]=cnt;} void dfs(int x,int fath) { siz[x]=1; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(y==fath) continue; fa[y]=x,dfs(y,x),siz[x]+=siz[y]; if(siz[y]>siz[son[x]]) son[x]=y; } } void dfs2(int x,int topf) { dfn[x]=++num,tp[x]=topf,rnk[num]=x; if(son[x]) dfs2(son[x],topf); else ed[topf]=x; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(!dfn[y]) dfs2(y,y); } } void dfs3(int x,int fath) { f[x][1]=a[x]; val[dfn[x]].c[1][0]=a[x]; for(int i=head[x];i;i=nxt[i]) { int y=to[i]; if(y==fath) continue; dfs3(y,x); f[x][1]+=f[y][0],f[x][0]+=max(f[y][0],f[y][1]); if(y!=son[x]) val[dfn[x]].c[1][0]+=f[y][0],val[dfn[x]].c[0][0]+=max(f[y][0],f[y][1]); } val[dfn[x]].c[0][1]=val[dfn[x]].c[0][0]; val[dfn[x]].c[1][1]=-INF; } void build(int p,int l,int r) { if(l==r) { t[p]=val[l]; return; } int mid=(l+r)>>1; build(p*2,l,mid),build(p*2+1,mid+1,r); t[p]=t[p*2]*t[p*2+1]; } void change(int p,int l,int r,int x) { if(l==r) { t[p]=val[l]; return; } int mid=(l+r)>>1; if(x<=mid) change(p*2,l,mid,x); else change(p*2+1,mid+1,r,x); t[p]=t[p*2]*t[p*2+1]; } matrix query(int p,int l,int r,int ql,int qr) { if(ql<=l&&r<=qr) return t[p]; int mid=(l+r)>>1; if(qr<=mid) return query(p*2,l,mid,ql,qr); if(mid<ql) return query(p*2+1,mid+1,r,ql,qr); return query(p*2,l,mid,ql,qr)*query(p*2+1,mid+1,r,ql,qr); } matrix ask(int x) { return query(1,1,n,dfn[x],dfn[ed[x]]); } void path_change(int u,int x) { val[dfn[u]].c[1][0]+=x-a[u]; a[u]=x; while(u) { matrix od=ask(tp[u]); change(1,1,n,dfn[u]); //1. 修改 2. 把val_x的值赋给data_x matrix nw=ask(tp[u]); u=fa[tp[u]]; val[dfn[u]].c[0][0]=val[dfn[u]].c[0][0]-max(od.c[0][0],od.c[1][0])+max(nw.c[0][0],nw.c[1][0]); val[dfn[u]].c[0][1]=val[dfn[u]].c[0][0]; val[dfn[u]].c[1][0]=val[dfn[u]].c[1][0]-od.c[0][0]+nw.c[0][0]; } } signed main() { // freopen("data.in","r",stdin); // freopen("own.out","w",stdout); n=read(),m=read(); for(int i=1;i<=n;i++) a[i]=read(); for(int i=1;i<n;i++) { int x=read(),y=read(); add(x,y),add(y,x); } dfs(1,0),dfs2(1,1),dfs3(1,0); build(1,1,n); for(int i=1;i<=m;i++) { int u=read(),x=read(); path_change(u,x); matrix t=ask(1); printf("%lld\n",max(t.c[0][0],t.c[1][0])); } }

__EOF__

本文作者仰望星空的蚂蚁
本文链接https://www.cnblogs.com/cqbzly/p/17530348.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   仰望星空的蚂蚁  阅读(22)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· DeepSeek 开源周回顾「GitHub 热点速览」
点击右上角即可分享
微信分享提示