學習筆記第二十九節:動態Dp
正題
因為NOIP2018考了這一個東西,所以不得不學。
我們以這一題為例題來引入今天的學習:【模板】動態dp
我們顯然可以用樹形Dp去做,倒不如我們先把方程列出來。
這兩條公式挺顯然的吧。
假設我們現在無聊,往樹鏈剖分的角度去考慮。
那麼我們設一個數組g,表示的是從非重兒子轉移來的狀態,跟f陣列的轉移方程類似。
我們可以把上面兩條公式改寫成什麼呢?
這兩條也是挺顯然的吧。
那麼這個東西有什麼用呢?
對於一條重鏈來說,g肯定是不會改變的,但是f是可能改變的。
我們試著去構造一個轉移矩陣:
我們定義這個乘法的含義是,那麼
然後化簡出來出來就是上面那兩條公式。
這個東西有什麼用?
容易發現等於x所在重鏈底端到x的矩陣連乘積。這個矩陣指的是中間的哪一個矩陣,也就是對於x所在重鏈的底端到x的任一點p,。
這個也是挺顯然的吧,因為每一個點存的都是非重兒子的答案,那麼多個非重兒子的答案合併就變成了該子樹的答案。
那麼我們把一個點的權值變成了所在重鏈的乘積。(注意,上面的乘積和pi,都是新定義的*)
假設我們要改x點的權值,那麼x所在的矩陣要修改吧,那麼就變成了改點。
我們還可以求出不在這條重鏈上的第一個祖先,那麼這個祖先的矩陣也要改,具體怎麼改,可以通過原來x點所在重鏈的top的子樹的答案的變化量來修改。
一直這樣往上跳,一共會改log個節點,因為要訪問區間連乘積,所以用線段樹維護,時間複雜度就是:。
雖然樹鏈剖分的常數不大,但是這個log平方的演算法已經夠卡常的了。
我還沒有學會全域性平衡二叉樹。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define lc (now<<1)
#define rc ((now<<1)|1)
using namespace std;
int n,m;
int w[100010];
struct edge{
int y,next;
}s[200010];
struct matrix{
int op[3][3];
matrix operator*(const matrix b)const{
matrix q;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++){
q.op[i][j]=-1e9;
for(int k=1;k<=2;k++)
q.op[i][j]=max(q.op[i][j],op[i][k]+b.op[k][j]);
}
return q;
}
}tr[300010];
int first[100010],len=0;
int tot[100010],son[100010];
int image[100010],fact[100010],last[100010],fa[100010],top[100010];
int f[100010][2],g[100010][2];
void ins(int x,int y){
len++;
s[len]=(edge){y,first[x]};first[x]=len;
}
void dfs_1(int x){
tot[x]=1;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa[x]) continue;
fa[y]=x;
dfs_1(y);
tot[x]+=tot[y];
if(tot[y]>tot[son[x]]) son[x]=y;
}
}
void dfs_2(int x,int tp){
image[x]=++len;fact[len]=x;top[x]=tp;
if(son[x]!=0) dfs_2(son[x],tp);
else {last[tp]=x;return ;}
for(int i=first[x];i!=0;i=s[i].next){
if(s[i].y==fa[x] || s[i].y==son[x]) continue;
dfs_2(s[i].y,s[i].y);
}
}
void tr_dp(int x){
f[x][1]=g[x][1]=w[x];
f[x][0]=g[x][0]=0;
for(int i=first[x];i!=0;i=s[i].next){
int y=s[i].y;
if(y==fa[x]) continue;
tr_dp(y);
f[x][0]+=max(f[y][0],f[y][1]);
f[x][1]+=f[y][0];
if(y==son[x]) continue;
g[x][0]+=max(f[y][0],f[y][1]);
g[x][1]+=f[y][0];
}
}
void update(int now,int l,int r,int x){
if(l==r){
tr[now].op[1][1]=tr[now].op[1][2]=g[fact[x]][0];
tr[now].op[2][1]=g[fact[x]][1];tr[now].op[2][2]=-1e9;
return ;
}
int mid=(l+r)/2;
if(x<=mid) update(lc,l,mid,x);
else update(rc,mid+1,r,x);
tr[now]=tr[lc]*tr[rc];
}
matrix query(int now,int x,int y,int l,int r){
if(x==l && y==r) return tr[now];
int mid=(l+r)/2;
if(y<=mid) return query(lc,x,y,l,mid);
else if(mid<x) return query(rc,x,y,mid+1,r);
else return query(lc,x,mid,l,mid)*query(rc,mid+1,y,mid+1,r);
}
void build(int now,int l,int r){
if(l==r){
tr[now].op[1][1]=tr[now].op[1][2]=g[fact[l]][0];
tr[now].op[2][1]=g[fact[l]][1];tr[now].op[2][2]=-1e9;
return ;
}
int mid=(l+r)/2;
build(lc,l,mid);build(rc,mid+1,r);
tr[now]=tr[lc]*tr[rc];
}
void change(int x,int y){
g[x][1]+=y-w[x];
w[x]=y;
matrix A,B;
int st,ed;
while(1){
st=top[x];ed=last[top[x]];
A=query(1,image[st],image[ed],1,n);
update(1,1,n,image[x]);
B=query(1,image[st],image[ed],1,n);
x=fa[st];
if(x==0) break;
g[x][0]+=max(B.op[1][1],B.op[2][1])-max(A.op[1][1],A.op[2][1]);
g[x][1]+=B.op[1][1]-A.op[1][1];
}
}
int main(){
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&w[i]);
int x,y;
for(int i=1;i<=n-1;i++) {
scanf("%d %d",&x,&y);
ins(x,y);ins(y,x);
}
len=0;
dfs_1(1);dfs_2(1,1);
tr_dp(1);
build(1,1,n);
for(int i=1;i<=m;i++){
scanf("%d %d",&x,&y);
change(x,y);
matrix A=query(1,image[1],image[last[1]],1,n);
printf("%d\n",max(A.op[1][1],A.op[2][1]));
}
}