【ZJOI2018】历史【LCT】【树形DP】
Decription
-
给定一棵树以及树上的每个节点的\(access\)次数,请合理安排\(access\)顺序以最大化虚实链的切换次数,需要支持单点修改。
Solution
首先考虑不带修改的情况:
对于节点\(u\),\(u\)点的虚实链切换次数至于\(u\)子树内的点有关,每个点是独立的,可以分开计算。而\(u\)节点的每次切换一定意味着出现了先后两个\(access\)来自\(u\)不同的子树,考虑最优策略,设\(mx\)为\(u\)的每个子树的\(access\)次数中最大的一个,\(sum\)为它们\(access\)次数的总和。
那么当\(2mx\leq sum\)时,我们总能找到方法使得任意相邻的两个\(access\)都来自不同子树,贡献为\(sum-1\),否则最后几个只能是连续的\(mx\)了,贡献为\(2(sum-mx)\),因此总贡献就是\(\min(2(sum-mx),sum-1)\),于是直接树形\(DP\)一遍即可。
接着考虑带修改的情况:
修改节点\(u\)时,显然只会影响它的祖先的贡献。
对于节点\(u\)以及它的儿子\(v\),如果\(2s_v\)(\(s_u\)为\(u\)子树的\(access\)次数之和)\(> s_u\),因此\(u\)的贡献为\(2(s_u-s_v)\),那么接下来如果\(v\)子树中的点进行修改,增加了\(w\),\(2(s_u-s_v)\)依然比\(sum-1\)大,\(u\)的贡献不会改变。这种情况下从\(u\)向\(v\)连一条实边,其他边为虚边,于是我们修改节点\(x\)时只会影响其到根路径上所有虚边的顶点的答案,也只会有可能将这些虚边变为实边。
而每条路径上的虚边数量一定\(<log \sum a\),证明与树剖类似,至于找虚边则直接\(access\)即可。
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=4e5+10;
int n,m,first[N],cnt,fa[N];
ll a[N],s[N],vs[N],ans;
struct node{
int v,nxt;
}e[N<<1];
inline void add(int u,int v){e[++cnt].v=v;e[cnt].nxt=first[u];first[u]=cnt;}
int ch[N][2];
inline bool isroot(int x){return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;}
inline bool which(int x){return ch[fa[x]][1]==x;}
inline void pushup(int u){s[u]=vs[u]+s[ch[u][0]]+s[ch[u][1]]+a[u];}
inline void Rotate(int x){
int y=fa[x],z=fa[y],wx=which(x),wy=which(y);
fa[x]=z;if(!isroot(y)) ch[z][wy]=x;
ch[y][wx]=ch[x][wx^1];fa[ch[x][wx^1]]=y;
ch[x][wx^1]=y;fa[y]=x;
pushup(y);pushup(x);
}
inline void Splay(int x){
while(!isroot(x)){
int y=fa[x];
if(!isroot(y)) which(x)==which(y)?Rotate(y):Rotate(x);
Rotate(x);
}
pushup(x);
}
inline void solve(int u){
s[u]=a[u];
ll mx=a[u],pos=0;
for(int i=first[u];i;i=e[i].nxt){
int v=e[i].v;
if(v==fa[u]) continue;
fa[v]=u;solve(v);
s[u]+=s[v];
if(s[v]>mx) mx=s[v],pos=v;
}
ans+=min(s[u]-1,(s[u]-mx)<<1);
if((mx<<1)>=s[u]+1) ch[u][1]=pos;
vs[u]=s[u]-a[u]-s[ch[u][1]];
}
inline ll calc(int u,ll sum,ll mx){
return mx?(sum-mx)<<1:((a[u]<<1)>=sum+1)?(sum-a[u])<<1:sum-1;
}
inline void access(int x,ll w){
Splay(x);
ll sum=s[x]-s[ch[x][0]],mx=s[ch[x][1]];
ans-=calc(x,sum,mx);
a[x]+=w;sum+=w;
if((mx<<1)<sum+1) ch[x][1]=0,vs[x]+=mx,mx=0;
pushup(x);ans+=calc(x,sum,mx);
int last=x;x=fa[x];
for(;x;last=x,x=fa[x]){
Splay(x);
ll sum=s[x]-s[ch[x][0]],mx=s[ch[x][1]];
ll rec=ans;
ans-=calc(x,sum,mx);
s[x]+=w;sum+=w;vs[x]+=w;
if((mx<<1)<sum+1) ch[x][1]=0,vs[x]+=mx,mx=0;
if((s[last]<<1)>=sum+1) ch[x][1]=last,vs[x]-=s[last],mx=s[last];
pushup(x);ans+=calc(x,sum,mx);
}
}
此处省略了一个iobuff
int main(){
// freopen("b4.in","r",stdin);
read(n);read(m);
for(int i=1;i<=n;++i) read(a[i]);
for(int i=1,u,v;i<n;++i) read(u),read(v),add(u,v),add(v,u);
solve(1);
putint(ans,'\n');ll w;
for(int i=1,x;i<=m;++i){
read(x);read(w);
access(x,w);
putint(ans,'\n');
}
flush();
return 0;
}