【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;
} 
posted @ 2021-03-18 18:26  cjTQX  阅读(31)  评论(0编辑  收藏  举报