1. 程式人生 > >動態DP(學習筆記)

動態DP(學習筆記)

一道模板題

動態 d p dp 是貓學長髮明的用來解決樹上帶修DP的問題的演算法。

好像多數是求樹上最大權獨立集?
樹上最大權獨立集我們可以用樹形 d p O

( n ) dpO(n) 地求出來,設 f [ u ] [
0 / 1 ] f[u][0/1]
表示 u u 為根的子樹 u
u
選或不選的最優方案,可以列出轉移式:
f [ u ] [ 0 ] + = m a x ( f [ v ] [ 1 ] , f [ v ] [ 0 ] ) f[u][0]+=max(f[v][1],f[v][0])
f [ u ] [ 1 ] + = f [ v ] [ 0 ] f[u][1]+=f[v][0]

如果帶修改該如何做呢,這個時候就要用樹剖+線段樹+矩陣來解決了。

樹剖+線段樹是常用的解決樹上問題的優秀演算法,想想重鏈一定是一個區間,就可以用線段樹來維護,那麼每個節點只需要維護其他輕兒子,可以把上面的轉移式改一改:
f [ u ] [ 0 ] = v l i g h t s o n ( u ) m a x ( f [ v ] [ 1 ] , f [ v ] [ 0 ] ) f[u][0]=\sum_{v\in lightson(u)}max(f[v][1],f[v][0])
f [ u ] [ 1 ] = a [ u ] + v l i g h t s o n ( u ) f [ v ] [ 0 ] f[u][1]=a[u]+\sum_{v\in lightson(u)}f[v][0]
把新的 f f 記為 g g
這樣就實現了維護一棵樹到維護一個序列的轉變

這個轉移怎麼轉化成矩陣的形式?
我們可以改變一下矩陣乘法的運算:

Mat operator *(const Mat &x) const{
		Mat ret;
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++)
				for(int k=0;k<2;k++)
				ret.g[i][j]=max(ret.g[i][j],g[i][k]+x.g[k][j]);
		return ret;
	}

然後想想矩陣是什麼:
[ f i , 0 f i , 1 ] = [ f i 1 , 0 f i 1 , 1 ] × [ g i , 0 g i , 0 g i , 1 ] \begin{bmatrix}f_{i,0}\\f_{i,1}\end{bmatrix} =\begin{bmatrix}f_{i-1,0}\\f_{i-1,1}\end{bmatrix}\times \begin{bmatrix}g_{i,0}&amp;g_{i,0}\\g_{i,1}&amp;-\infty \end{bmatrix}
然後代入矩陣乘法算一算,頓時覺得很有道理啊

於是可以用線段樹維護每個區間的矩陣積,查詢的時候只需要線上段樹上 q u e r y query 就好了

然後修改怎麼做呢?可以發現當一個節點的權值改變了,那麼它的鏈頂的節點的 f f 值會改變,再往上,每過一條輕邊,它都會影響那個點的 g g 值,線上段樹上單點修改就好了,因為輕邊是 l o g n logn 級別的,線段樹修改也是 l o g n logn 級別的,所以修改複雜度是 n l o g 2 n nlog^2n 的,查詢 n l o g n nlogn ,總複雜度 O ( n l o g 2 n ) O(nlog^2n)

程式碼如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define LL long long
#define N 100005
#define ls cur<<1
#define rs cur<<1|1
#define inf 0x3f3f3f3f
using namespace std;

inline int rd(){
	int x=0,f=1;char c=' ';
	while(c<'0' || c>'9') f=c=='-'?-1:1,c=getchar();
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

int n,m,a[N],cnt,head[N],f[N][2];
int dfn[N],rk[N],dep[N],fa[N],son[N],siz[N],top[N],ed[N],num;

struct EDGE{
	int to,nxt;
}edge[N<<1];
inline void add(int x,int y){
	edge[++cnt].to=y; edge[cnt].nxt=head[x]; head[x]=cnt;
}

void dfs1(int u,int fat){
	siz[u]=1; int maxson=-1;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to; if(v==fat) continue; fa[v]=u;
		dep[v]=dep[u]+1; dfs1(v,u); siz[u]+=siz[v];
		if(siz[v]>maxson) maxson=siz[v],son[u]=v;
	} return;
}

void dfs2(int u,int t){
	top[u]=t; dfn[u]=++num; rk[num]=u; ed[t]=u;
	if(!son[u]) return;
	dfs2(son[u],t);
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(!dfn[v]) dfs2(v,v);
	} return;
}

struct Mat{
	int g[2][2];
	Mat(){memset(g,0,sizeof g);}
	Mat operator *(const Mat &x) const{
		Mat ret;
		for(int i=0;i<2;i++)
			for(int j=0;j<2;j++)
				for(int k=0;k<2;k++)
				ret.g[i][j]=max(ret.g[i][j],g[i][k]+x.g[k][j]);
		return ret;
	}
}val[N],node[N<<2];

void build(int cur,int L,int R){
	if(L==R){
		int g0=0,g1=a[rk[L]];
		for(int u=rk[L],i=head[u],v;i;i=edge[i].nxt)
			if((v=edge[i].to)!=fa[u] && v!=son[u])
				g0+=max(f[v][0],f[v][1]),g1+=f[v][0];
		node[cur]