动态 Dp
以 P4719【模板】动态 DP 一题为例。
我们显然可以写出朴素的方程。
考虑有 \(v\) 为 \(u\) 儿子。
\(f_{u,0}=\sum \max(f_{v,0},f_{v,1})\)
\(f_{u,1}=\sum f_{v,0}\).
答案是 \(\max (f_{root,0},f_{root,1})\).
加上修改操作呢,我们可以使用树链剖分。若 \(son_u\) 为重儿子。
设 \(g_{u,0}\) 表示不考虑 \(son_u\) 的情况下,不选择 \(u\) 的最大答案,
\(g_{u,1}\) 表示不考虑 \(son_u\) 的情况下,选择 \(u\) 的最大答案。
\(f_{u,0}=g_{u,0}+\max(f_{son_u,0},f_{son_u,1})\)
\(f_{u,1}=g_{u,1}+f_{son_u,0}\)
可以构造出矩阵:
定义广义矩阵乘法 \(A\times B=C\) 为:
\(C_{i,j}=\max_{k=1}^{n}(A_{i,k}+B_{k,j})\)
所以写出方程:
\( \begin{bmatrix} g_{u,0} & g_{u,0}\\ g_{u,1} & -\infty \end{bmatrix}\times \begin{bmatrix} f_{son_u,0}\\f_{son_u,1} \end{bmatrix}= \begin{bmatrix} f_{u,0}\\f_{u,1} \end{bmatrix} \)
这里的矩阵乘法是满足结合律的,所以可以区间维护。
刚好就用线段树。
那我们怎么修改呢?可以发现,修改操作时只需要修改 \(g_{u,1}\) 和每条往上的重链即可。
code
#include<bits/stdc++.h>
using namespace std;
const int N=100000+5,M=200000+5;
const int V=400000+5;
const int inf=0x7f7f7f7f;
struct Mat {
int a[2][2];
Mat() {memset(a,-0x3F,sizeof a);}
Mat operator * (Mat b) {
Mat c;
for(int i=0; i<2; i++)
for(int j=0; j<2; ++j)
for(int k=0; k<2; ++k)
c.a[i][j]=max(c.a[i][j],a[i][k]+b.a[k][j]);
return c;
}
};
int n,m,num,tot;
int a[N];
int fa[N],siz[N],dep[N],son[N];
int top[N],id[N],dfn[N],ed[N];
int F[N][2];
int head[N],ver[M],nxt[M];
Mat Val[N];
struct SegTree {
int L[V],R[V];
Mat m[V];
void Push_up(int p) {
m[p]=m[p<<1]*m[p<<1|1];
}
void Build_Tree(int l,int r,int p) {
L[p]=l,R[p]=r;
if(L[p]==R[p]) {
m[p]=Val[dfn[L[p]]];
return;
}
int mid=(L[p]+R[p])>>1;
Build_Tree(L[p],mid,p<<1);
Build_Tree(mid+1,R[p],p<<1|1);
Push_up(p);
}
void Update_Tree(int x,int p) {
if(L[p]==R[p]) {
m[p]=Val[dfn[x]];
return;
}
int mid=(L[p]+R[p])>>1;
if(x<=mid) Update_Tree(x,p<<1);
else Update_Tree(x,p<<1|1);
Push_up(p);
}
Mat Query_Tree(int l,int r,int p) {
if(L[p]==l&&R[p]==r) return m[p];
int mid=(L[p]+R[p])>>1;
if(r<=mid)
return Query_Tree(l,r,p<<1);
else if(l>mid)
return Query_Tree(l,r,p<<1|1);
else
return Query_Tree(l,mid,p<<1)*Query_Tree(mid+1,r,p<<1|1);
}
} T;
void add(int x,int y) {
ver[++tot]=y; nxt[tot]=head[x]; head[x]=tot;
}
void dfs1(int u) {
siz[u]=1;
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(v==fa[u]) continue;
fa[v]=u; dep[v]=dep[u]+1;
dfs1(v);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int topf) {
num++;
id[u]=num; dfn[num]=u;
top[u]=topf;
ed[topf]=max(ed[topf],num);
F[u][0]=0,F[u][1]=a[u];
Val[u].a[0][0]=Val[u].a[0][1]=0;
Val[u].a[1][0]=a[u];
if(son[u]!=0) {
dfs2(son[u],topf);
F[u][0]+=max(F[son[u]][0],F[son[u]][1]);
F[u][1]+=F[son[u]][0];
}
for(int i=head[u]; i; i=nxt[i]) {
int v=ver[i];
if(v==fa[u]||v==son[u]) continue;
dfs2(v,v);
F[u][0]+=max(F[v][0],F[v][1]);
F[u][1]+=F[v][0];
Val[u].a[0][0]+=max(F[v][0],F[v][1]);
Val[u].a[0][1]=Val[u].a[0][0];
Val[u].a[1][0]+=F[v][0];
}
}
void init() {
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%d",&a[i]);
for(int i=1,u,v; i<n; i++) {
scanf("%d%d",&u,&v);
add(u,v); add(v,u);
}
dfs1(1); dfs2(1,1);
}
void update_path(int u,int w) {
Val[u].a[1][0]+=w-a[u];
a[u]=w;
Mat bef,aft;
while(u!=0) {
bef=T.Query_Tree(id[top[u]],ed[top[u]],1);
T.Update_Tree(id[u],1);
aft=T.Query_Tree(id[top[u]],ed[top[u]],1);
u=fa[top[u]];
Val[u].a[0][0]+=max(aft.a[0][0],aft.a[1][0])-max(bef.a[0][0],bef.a[1][0]);
Val[u].a[0][1]=Val[u].a[0][0];
Val[u].a[1][0]+=aft.a[0][0]-bef.a[0][0];
}
}
void solve() {
T.Build_Tree(1,n,1);
for(int i=1; i<=m; i++) {
int u,w;
scanf("%d%d",&u,&w);
update_path(u,w);
Mat ans=T.Query_Tree(id[1],ed[1],1);
printf("%d\n",max(ans.a[0][0],ans.a[1][0]));
}
}
int main() {
init();
solve();
return 0;
}