[ZJOI2015] 幻想乡战略游戏

\(\text{Problem}:\)[ZJOI2015] 幻想乡战略游戏

\(\text{Solution}:\)

设当前补给站 \(u\) 为根,\(siz_{u}\) 表示 \(u\) 子树内点权之和。考虑从 \(u\rightarrow v\in son_{u}\),花费的增量为 \(val_{edge}(siz_{u}-2siz_{v})\)。即当 \(2siz_{v}>siz_{u}\) 时,从 \(u\) 转移到 \(v\) 更优,而这样的 \(v\) 至多有一个,这提示我们可以从根结点贪心地转移求得答案。

不难发现,这种做法的复杂度与树高和每个结点的度数有关,故考虑建出点分树。在点分树上维护 \(siz_{x}\) 表示 \(x\) 子树内点权和,\(f_{x}\) 表示 \(x\) 子树内到 \(x\) 的花费之和,\(g_{x}\) 表示 \(x\) 子树内到 \(fa_{x}\) 的花费之和即可快速维护对答案的贡献。具体实现可参考代码。

注意跳点分树时,比较的是原树上的结点,跳的是点分树上的结点。总时间复杂度 \(O(20n\log^2n)\),可以通过。

\(\text{Code}:\)

#include <bits/stdc++.h>
#pragma GCC optimize(3)
#define int long long
#define ri register
#define mk make_pair
#define fi first
#define se second
#define pb push_back
#define eb emplace_back
#define is insert
#define es erase
#define vi vector<int>
#define vpi vector<pair<int,int>>
using namespace std; const int N=200010, M=20;
inline int read()
{
	int s=0, w=1; ri char ch=getchar();
	while(ch<'0'||ch>'9') { if(ch=='-') w=-1; ch=getchar(); }
	while(ch>='0'&&ch<='9') s=(s<<3)+(s<<1)+(ch^48), ch=getchar();
	return s*w;
}
int n,Q,maxx,root,siz[N],book[N],f[N];
int dep[N],dis[N],st[N][M],tot,lg[N],id[N];
int g1[N],g2[N],g3[N];
struct Graph
{
	int head[N],maxE; struct Edge { int nxt,to,rdis; }e[N<<1];
	inline void Add(int u,int v,int w) { e[++maxE].nxt=head[u]; head[u]=maxE; e[maxE].to=v; e[maxE].rdis=w; }
}A,B;
void DFS1(int x,int fa)
{
	dep[x]=dep[fa]+1;
	st[++tot][0]=x;
	id[x]=tot;
	for(ri int i=A.head[x];i;i=A.e[i].nxt)
	{
		int v=A.e[i].to;
		if(v==fa) continue;
		dis[v]=dis[x]+A.e[i].rdis;
		DFS1(v,x);
		st[++tot][0]=x;
	}
}
inline int Min(int x,int y) { return dep[x]<dep[y]?x:y; }
inline int LCA(int x,int y)
{
	x=id[x], y=id[y];
	if(x>y) swap(x,y);
	int k=lg[y-x+1];
	return Min(st[x][k],st[y-(1ll<<k)+1][k]);
}
inline int Dis(int x,int y) { return dis[x]+dis[y]-dis[LCA(x,y)]*2; }
inline void Init()
{
	lg[0]=-1;
	for(ri int i=1;i<N;i++) lg[i]=lg[i>>1]+1;
	DFS1(1,0);
	for(ri int i=1;(1ll<<i)<=tot;i++)
	for(ri int j=1;j+(1ll<<i)-1<=tot;j++)
	st[j][i]=Min(st[j][i-1],st[j+(1ll<<(i-1))][i-1]);
}
void Findroot(int x,int fa,int S)
{
	int cs=0; siz[x]=1;
	for(ri int i=A.head[x];i;i=A.e[i].nxt)
	{
		int v=A.e[i].to;
		if(v==fa||book[v]) continue;
		Findroot(v,x,S);
		siz[x]+=siz[v];
		cs=max(cs,siz[v]);
	}
	cs=max(cs,S-siz[x]);
	if(cs<maxx) maxx=cs, root=x;
}
void Build(int x,int S)
{
	book[x]=1;
	for(ri int i=A.head[x];i;i=A.e[i].nxt)
	{
		int v=A.e[i].to;
		if(book[v]) continue;
		maxx=1e9, root=0;
		int vsz=siz[v]>siz[x]?S-siz[x]:siz[v];
		Findroot(v,x,vsz);
		f[root]=x, B.Add(x,root,v), B.Add(root,x,v);
		Build(root,vsz);
	}
	root=x;
}
inline void UpDate(int u,int w)
{
	int x=u;
	g3[x]+=w;
	while(f[x])
	{
		int y=f[x];
		g3[y]+=w;
		int dd=Dis(y,u);
		g1[y]+=dd*w;
		g2[x]+=dd*w;
		x=f[x];
	}
}
inline int Ask(int u)
{
	int tt=g1[u];
	int x=u;
	while(f[x])
	{
		int y=f[x];
		tt+=g1[y]-g2[x];
		int dd=Dis(y,u);
		tt+=dd*(g3[y]-g3[x]);
		x=f[x];
	}
	return tt;
}
int Query(int x)
{
	int now=Ask(x);
	for(ri int i=B.head[x];i;i=B.e[i].nxt)
	{
		int v=B.e[i].to;
		if(Ask(B.e[i].rdis)<now) return Query(v);
	}
	return now;
}
signed main()
{
	n=read(), Q=read();
	for(ri int i=1;i<n;i++)
	{
		int u,v,w;
		u=read(), v=read(), w=read();
		A.Add(u,v,w), A.Add(v,u,w);
	}
	Init();
	maxx=1e9, Findroot(1,0,n);
	Build(root,n);
	for(ri int i=1;i<=Q;i++)
	{
		int x,w;
		x=read(), w=read();
		UpDate(x,w);
		printf("%lld\n",Query(root));
	}
	return 0;
}
posted @ 2021-04-28 16:38  zkdxl  阅读(66)  评论(1编辑  收藏  举报