[ZJOI2018] 历史

一、题目

点此看题

二、解法

可以用拆贡献的方法把最后的最后的答案分解开来,考虑点 \(u\) 的切换次数,如果相邻两个切换点来自同一个子树,那么点 \(u\) 不会产生任何贡献(有贡献也一定在子树内就算过了),如果相邻切换点来自不同子树那么会有 \(1\) 的贡献。注意这个相邻的意思并不是实际相邻,而是只考虑子树修改的情况下会相邻,设 \(s[u]\) 表示子树内的 \(a\) 求和,\(mx[u]\) 表示儿子最大的 \(a[v]\) 那么答案为:

\[\min(2(s[u]-mx[u]),s[u]-1) \]

可以证明子树内用最优的安排顺序不会对子树外的最优安排造成影响

现在的问题是单点修改,维护上面柿子的和,由于实际维护中取最小值是难以实现的,所以我们考虑什么时候最小值才会取到第一项:

\[2(s[u]-mx[u])\leq s[u]-1\Rightarrow 2\cdot mx[u]\geq s[u]+1 \]

满足条件的 \(mx[u]\) 至多只有一个(只有是重儿子的时候才可能),看到 满足什么条件的儿子至多只有一个 这种东西就一定要注意啦,我们可以用 \(\tt lct\) 来维护,也就是向这一个点拉一条实边。

考虑修改,因为只会影响到根的这条路径,所以可以尝试用 \(\tt access\) 操作来维护修改,注意到如果是实边的话加上一个数是不会影响答案的,所以我们只需要暴力把所有虚边改一下就行,到根虚边又是均摊 \(O(\log n)\) 的所以时间合法。

具体来说我们还要维护维护 \(vs[u]\) 表示 \(u\) 的非实儿子的子树和,写的时候好像要涉及到对 \(s\) 的区间修改,这里推荐一种不要打标记的写法,我们把 \(x\) 转到根之后直接修改 \(s[x]\),这样由于 \(s\) 维护的是子树内的和所以深度比 \(x\) 小并且在 \(splay\) 上的节点也都会被改到(因为我们只有在转到根的时候才使用 \(s\))。那么就不需要打标记啦,时间复杂度 \(O(n\log n)\)

#include <cstdio>
const int M = 400005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,tot,f[M],a[M],s[M],vs[M],ch[M][2],par[M];
struct edge
{
	int v,next;
	edge(int V=0,int N=0) : v(V) , next(N) {}
}e[2*M];
int cal(int x,int t,int h)
//传参子树和,重儿子和 
{
	if(ch[x][1]) return 2*(t-h);
	if(2*a[x]>t) return 2*(t-a[x]);
	return t-1;
}
void dfs(int u,int fa)
{
	s[u]=a[u];
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==fa) continue;
		dfs(v,u);
		s[u]+=s[v];par[v]=u;
	}
	for(int i=f[u];i;i=e[i].next)
		if(e[i].v!=fa && 2*s[e[i].v]>s[u])//是重儿子
			ch[u][1]=e[i].v;//拉一条实边
	vs[u]=s[u]-s[ch[u][1]]-a[u];
	ans+=cal(u,s[u],s[ch[u][1]]);
}
int nrt(int x)
{
	return ch[par[x]][0]==x || ch[par[x]][1]==x;
}
int chk(int x)
{
	return ch[par[x]][1]==x;
}
void up(int x)
{
	s[x]=s[ch[x][0]]+s[ch[x][1]]+a[x]+vs[x];
}
void rotate(int x)
{
	int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
	ch[y][k]=w;par[w]=y;
	if(nrt(y)) ch[z][chk(y)]=x;par[x]=z;
	ch[x][k^1]=y;par[y]=x;
	up(y);up(x);
}
void splay(int x)
{
	while(nrt(x))
	{
		int y=par[x],z=par[y];
		if(nrt(y))
		{
			if(chk(x)==chk(y)) rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
void access(int x,int w)
{
	splay(x);
	int now=s[x]-s[ch[x][0]],mson=s[ch[x][1]];
	ans-=cal(x,now,mson);//减掉原来的信息 
	a[x]+=w;s[x]+=w;now+=w;//修改信息 
	if(2*mson<=now) vs[x]+=mson,ch[x][1]=mson=0;//看原来的重儿子是否变化 
	ans+=cal(x,now,mson);up(x);
	int y=x;
	for(x=par[x];x;x=par[y=x])
	{
		splay(x);
		now=s[x]-s[ch[x][0]];mson=s[ch[x][1]];
		ans-=cal(x,now,mson);
		s[x]+=w;vs[x]+=w;now+=w;
		if(mson*2<=now) vs[x]+=mson,ch[x][1]=mson=0;
		if(s[y]*2>now) vs[x]-=s[y],ch[x][1]=y,mson=s[y];
		ans+=cal(x,now,mson);up(x);
	}
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge(v,f[u]),f[u]=tot;
		e[++tot]=edge(u,f[v]),f[v]=tot;
	}
	dfs(1,0);
	printf("%lld\n",ans);
	while(m--)
	{
		int x=read(),w=read();
		access(x,w);
		printf("%lld\n",ans);
	}
}
posted @ 2021-03-17 17:46  C202044zxy  阅读(86)  评论(0编辑  收藏  举报