題目大意
給定一棵 個點的樹,點帶點權。有 次操作,每次操作給定 ,表示修改點 的權值爲 ,輸出每次修改後樹的最大權獨立集大小。
數據範圍
題解
首先如果不考慮修改操作,很容易想到動態規劃,首先是定義
表示不取 節點時,以 爲根的子樹的最大獨立集大小。
表示取 節點時,以 爲根的子樹的最大獨立集大小。
定義 的所有兒子的集合爲 ,自身的權值爲 ,則轉移如下(其實是廢的)1
可以發現,每次的修改操作只會影響到某個點到根節點上所有點的 數組,這是樹上的其中一條鏈,接下來我們可以考慮樹鏈剖分。爲了適應樹鏈剖分的定義,我們再從 中分離出一部分信息,我們定義 的重兒子爲 ,以它爲根的子樹爲 ,則可以定義
表示不取 節點時,以 爲根的子樹,在不考慮子樹 的前提下,的最大獨立集大小。
表示取 節點時,以 爲根的子樹,在不考慮子樹 的前提下,的最大獨立集大小。
簡而言之, 就是不考慮重兒子的貢獻的 。 的轉移如下
同樣地, 的轉移可以更簡單。
上面的轉移中,第一個等於號的右側應該是顯然的,第二個等於號後面是喫飽了撐的,但絕不是無聊之作。考慮樹鏈剖分的操作,我們需要一種數據結構來維護這些信息,如果仍然採用線段樹的話,我們需要一種滿足結合律的運算,使得我們能保證結果的正確。
觀察一下,第二個等於號後面的式子有取最大值,有相加操作,還設計到多個元素的運算,顯然不可能爲簡單的四則運算,但其形式和矩陣乘法有些類似。於是,我們定義新矩陣乘法爲 可以證明,這樣的定義滿足結合律,仍然不滿足交換率。那麼上面的轉移可以寫成
定義上面方程左側含有 的矩陣爲矩陣 ,等式右側的矩陣爲 ,具體地,原樹中每個非葉子節點存儲的信息爲 ,特別地,葉子節點存儲的信息爲 。每個節點的信息均可以用一次普通樹形DP求出。
如此,我們想要知道根節點的 矩陣,只需要將根節點所在的鏈上所有節點的矩陣全部乘起來(從深度小的乘到深度大的)。這個過程需要我們先對原樹進行重鏈剖分,然後重編號,再用維護區間乘法的線段樹維護每條重鏈上的矩陣的信息。
然後,我們就成功地把一個線性的算法優化到了帶一個log的級別。 我們開始考慮修改操作。
我們定義 表示 所在的節點的重鏈的頂部的節點, 表示節點 的父親,修改點 的權值後, 的值會隨之更改,這個修改十分簡單,直接在線段樹上修改,這樣會改變 矩陣,進而影響到 。
每次都按照定義重新計算一次 顯然不現實,於是我們考慮在修改 之前,先將 的信息存下來,然後完修改 後,讓 加上 的變化量(先扣除原來的 ,再加上新的 )。
令整顆樹的根節點爲 ,則最後的答案便是 。
代碼
代碼本身不算長,和普通樹剖差不多,下面的代碼只是註釋和空行比較多。
#include<bits/stdc++.h>
using namespace std;
#define maxn 400010
struct matrix{//矩陣類
int mat[2][2];
matrix(){memset(mat,-63,sizeof mat);}//初始化爲無窮小
inline int* operator[](const int x){return mat[x];}//重載中括號運算符
inline matrix operator*(const matrix &x)const{//新·矩陣乘法
matrix c;
for(int i=0;i<=1;i++)
for(int j=0;j<=1;j++)
for(int k=0;k<=1;k++)
c[i][j]=max(c[i][j],mat[i][k]+x.mat[k][j]);
return c;
}
};
struct edge{//鄰接表
int v,next;
}edges[maxn<<1];
int head[maxn],cnte=0;
void ins(int u,int v){//建邊
edges[++cnte]=(edge){v,head[u]};
head[u]=cnte;
}
int fa[maxn],size[maxn],son[maxn];
//父節點,子樹大小,重兒子
void dfs(int u){//深搜求出基本信息
size[u]=1;
for(int i=head[u],v;i;i=edges[i].next)
if((v=edges[i].v)!=fa[u]){
fa[v]=u; dfs(v);
size[u]+=size[v];
if(size[son[u]]<size[v])
son[u]=v;
}
}
int n,q,a[maxn];
int idx[maxn],rank[maxn],cntv=0,top[maxn],end[maxn];
//idx:新編號 rank:原編號 top:重鏈頂部的原編號 end:重鏈尾的新編號
int f[maxn][2],g[maxn][2];
/*
f[u][0]表示不取點u時,以u爲根的子樹的最大獨立集大小
f[u][1]表示取點u時,以u爲根的子樹的最大權獨立集大小
g[u][0]表示不選點u,也不考慮重兒子所在子樹的前提下,以u爲根的子樹的最大權獨立集
g[u][1]表示選點u,不考慮重兒子所在子樹的前提下,以u爲根的子樹的最大權獨立集
g[u][0]=sigma{max(f[i][0],f[i][1])|i是u的兒子,i≠son[u]}
g[u][1]=a[i]+sigma{f[i][0]|i是u的兒子,i≠son[u]}
f[u][0]=max(g[u][0]+f[son[u]][0],g[u][0]+f[son[u]][1])
f[u][1]=max(g[i][1]+f[son[u]][0],-inf+f[son[u]][1])
|g[i][0] g[i][0] | * |f[son][0]| = |f[i][0]|
|g[i][1] -inf | |f[son][1]| |f[i][1]|
對於葉子節點,其沒有重兒子,因而其f和g的值沒有區別
g[leaf][0]=f[leaf][0]=0
g[leaf][1]=f[leaf][1]=a[leaf]
因而不需要特判
*/
matrix val[maxn];
//每個節點的矩陣
void recode(int u,int chain){//重編號+基本dp
rank[idx[u]=++cntv]=u; //標號
end[top[u]=chain]=cntv; //更新重鏈信息
g[u][0]=f[u][0]=0; //初始化
g[u][1]=f[u][1]=a[u];
if(son[u]){ //先遍歷重兒子
recode(son[u],chain);
f[u][0]+=max(f[son[u]][1],f[son[u]][0]); //計算f數組
f[u][1]+=f[son[u]][0];
//g數組不考慮重兒子
}
for(int i=head[u],v;i;i=edges[i].next)
if((v=edges[i].v)!=fa[u]&&v!=son[u]){
recode(v,v);
//計算f和g數組
f[u][0]+=max(f[v][0],f[v][1]);
g[u][0]+=max(f[v][0],f[v][1]);
f[u][1]+=f[v][0];
g[u][1]+=f[v][0];
}
//複製到val中
val[u][0][1]=val[u][0][0]=g[u][0];
val[u][1][0]=g[u][1];
val[u][1][1]=-1e9;
}
struct node{
node *left,*right;
int l,r;
matrix d;
node(int tl=0,int tr=0):l(tl),r(tr){//建樹
if(tl==tr){
d=val[rank[tl]];//線段樹葉子節點存儲每個點儲存的矩陣
left=right=NULL;
return;
}
int mid=(l+r)>>1;
left=new node(l,mid);
right=new node(mid+1,r);
d=left->d*right->d;
}
void change(int x){//更新
if(l==r){
d=val[rank[l]];//更新每個點儲存的矩陣
return;
}
if(x<=left->r) left->change(x);
else right->change(x);
d=left->d*right->d;
}
matrix query(int x,int y){//查詢
if(l==x&&r==y) return d;
if(y<=left->r) return left->query(x,y);
if(x>=right->l) return right->query(x,y);
return left->query(x,left->r)*right->query(right->l,y);
}
}t;
void change(int u,int d){//修改
val[u][1][0]+=d-a[u];//加上差值
a[u]=d;
while(u!=0){
matrix before=t.query(idx[top[u]],end[top[u]]);//計算出原來的
t.change(idx[u]);//修改
matrix after=t.query(idx[top[u]],end[top[u]]);//計算出後來的
u=fa[top[u]];//到另一條鏈上
val[u][0][0]+=max(after[0][0],after[1][0])-max(before[0][0],before[1][0]);//加上差值
val[u][0][1]=val[u][0][0];
val[u][1][0]+=after[0][0]-before[0][0];
}
}
int main(){
scanf("%d%d",&n,&q);
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);
ins(u,v),ins(v,u);
}
dfs(1);
recode(1,1);
t=node(1,n);
for(int i=1,u,d;i<=q;i++){
scanf("%d %d",&u,&d);
change(u,d);
matrix ans=t.query(idx[1],end[top[1]]);
printf("%d\n",max(ans[0][0],ans[1][0]));
}
return 0;
}
有兩條分割線分割的是最終使用的版本。 ↩︎