【总结】动态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]));
	}
}
posted @ 2021-05-02 22:51  仰望星空的蚂蚁  阅读(32)  评论(0)    收藏  举报  来源