P4338-[ZJOI2018]历史【LCT】

正题

题目链接:https://www.luogu.com.cn/problem/P4338


题目大意

给出\(n\)个点的一棵树,和每个点进行\(access\)的次数\(a_i\),要求安排一个顺序使得虚实边转换最多。

\(m\)次修改一个点让\(a_i\)加上\(w\)后求答案

\(n,m\in [1,4*10^5],a_i,w\in[1,10^7]\)


解题思路

好像本来就很麻烦还带修改,那先不考虑修改

考虑统计每个节点下的边的虚实切换最大化,可以发现一个节点的子树中无论怎样安排\(access\)顺序也不会影响外面的答案,因为对于外面的来说都相当于\(access\)了这个节点。

所以这个满足子最优?(好像是这么叫的),那每一个节点的分块考虑就好了。现在对于这个节点下的边,如果两次\(access\)的是在不同的儿子的子树中就会产生一点贡献。

转换一下现在的问题就是有若干种个数不同的颜色排成一排,要求相邻的异色最多。这个可以贪心解决,正常来说只要每次拿与上个不同的最多的来排就能到达\(sum-1\)的答案上线,但是需要特判一下如果最多的颜色个数\(mx\)\(2\times mx>sum\)那么此时这样排到最后还有一种颜色剩下,答案就是\(2\times (sum-mx)\)

所以一个节点的答案就是\(min\{sum-1,2\times (sum-mx)\}\)

但是带修改怎么搞,考虑到每次修改一定是加一个正权。

我们显然有一个式子

\[2\times mx> sum\Rightarrow 2\times (mx+c)> sum+c \]

这个式子表明如果一个节点选择了\(2\times (sum-mx)\)作为权值,那么它以后也都是这个权值。

\(s_x\)表示\(x\)子树中的权值和,对一个节点\(x\)定义\(r=max\{s_y(fa_y=x)\}\)\(sum=\sum_{fa_y=x}s_y\)

如果\(2\times mx>sum\)那么向\((x,y)\)连一条实边,其他儿子连虚边。否则全连虚边。

那么每次修改的过程中我们只需要遍历到根节点的虚边看是否需要切换即可,这个可以用\(LCT\)来维护。

而且因为每条虚边代表着\(2\times s_y\leq s_x\),所以路径上虚边的个数不会超过\(\log \sum a_i\)级别。

时间复杂度\(O(n\log \sum a_i)\)\(Splay\)那个\(\log n\)因为小于\(\log \sum a_i\)就舍去)


code

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const ll N=4e5+10;
struct node{
	ll to,next;
}a[N<<1];
ll n,m,s[N],ls[N],ans,tot;
struct LCT{
	ll fa[N],s[N],w[N],v[N],t[N][2];
	bool Nroot(ll x)
	{return fa[x]&&(t[fa[x]][0]==x||t[fa[x]][1]==x);}
	bool Direct(ll x){return t[fa[x]][1]==x;}
	void PushUp(ll x)
	{s[x]=s[t[x][0]]+s[t[x][1]]+w[x]+v[x];return;}
	void Rotate(ll x){
		ll y=fa[x],z=fa[y];
		ll xs=Direct(x),ys=Direct(y);
		ll w=t[x][xs^1];
		t[x][xs^1]=y;t[y][xs]=w;
		if(Nroot(y))t[z][ys]=x;
		fa[x]=z;fa[y]=x;if(w)fa[w]=y;
		PushUp(y);PushUp(x);
		return;
	}
	void Splay(ll x){
		while(Nroot(x)){
			ll y=fa[x];
			if(!Nroot(y))Rotate(x);
			else if(Direct(x)==Direct(y))
				Rotate(y),Rotate(x);
			else Rotate(x),Rotate(x);
		}
		return;
	}
	ll ct(ll x,ll r,ll h){
		if(t[x][1])return (r-h)*2;
		return min(r-1,(r-v[x])*2);
	}
	void Access(ll x,ll c){
		Splay(x);
		ll r=s[x]-s[t[x][0]],h=s[t[x][1]];
		ans-=ct(x,r,h);v[x]+=c;r+=c;PushUp(x);
		if(h*2<r+1)w[x]+=h,t[x][1]=0;
		ans+=ct(x,r,h);PushUp(x);ll y;
		for(y=x,x=fa[x];x;y=x,x=fa[x]){
			Splay(x);
			ll r=s[x]-s[t[x][0]],h=s[t[x][1]];
			ans-=ct(x,r,h);w[x]+=c;r+=c;
			if(h*2<r+1)w[x]+=h,t[x][1]=h=0;
			if(s[y]*2>r)w[x]-=s[y],t[x][1]=y,h=s[y];
			ans+=ct(x,r,h);PushUp(x);
		}
		return;
	}
}T;
void addl(ll x,ll y){
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
void dp(ll x,ll fa){
	ll son=0,mx=T.v[x]=s[x];T.fa[x]=fa;
	for(ll i=ls[x];i;i=a[i].next){
		ll y=a[i].to;
		if(y==fa)continue;
		dp(y,x);s[x]+=s[y];
		if(s[y]>mx)son=y,mx=s[y];
	}
	ans+=min(s[x]-1,(s[x]-mx)*2);
	if(mx*2>s[x])T.t[x][1]=son;
	T.w[x]=s[x]-T.v[x]-s[T.t[x][1]];
	T.s[x]=s[x];return;
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=n;i++)
		scanf("%lld",&s[i]);
	for(ll i=1;i<n;i++){
		ll x,y;
		scanf("%lld%lld",&x,&y);
		addl(x,y);addl(y,x);
	}
	dp(1,0);printf("%lld\n",ans);
	for(ll i=1;i<=m;i++){
		ll x,w;
		scanf("%lld%lld",&x,&w);
		T.Access(x,w);
		printf("%lld\n",ans);
	}
	return 0;
}
posted @ 2021-02-07 18:27  QuantAsk  阅读(80)  评论(0编辑  收藏  举报