Loading

[ZJOI2015] 幻想乡战略游戏

之前写过一遍点分树做法,但比较抽象,而且复杂度比较劣。今天学了一下树剖做法。

做法

有结论:只需要考虑点权,补给点设在带权重心上。

考虑反证法,令 \(x\) 表示重心,\(u\) 表示最优补给点,\(sz_i\) 表示 \(x\) 子树内点权之和,当 \(u\neq x\) 时,令 \(u\)\(x\) 方向的边为 \((u,v,w)\),如果把最优补给点移动到 \(v\),则对答案的贡献为 \(w\cdot(sz_u-sz_v)<0\),与最优的假设矛盾。

有了这一结论,考虑如何快速找重心。由于重心的每个子树大小都不超过 \(sz_{rt}/2\),显然有 \(2sz_x\ge sz_{rt}\),且 \(sz_x\) 最小,即 \(dep_x\) 最大。考虑在 DFS 序上做线段树二分,找到最后一个满足 \(2sz_x\ge sz_{rt}\) 的点即可。

考虑这样为什么是对的。当存在两个点使得 \(2sz_x=sz_{rt}\) 时,任取即可;否则,满足条件的点一定构成一条到根的链,因此 DFS 序最大的点就是深度最大的点。

找到重心后,考虑维护答案。有:

\[\large \begin{align*} ans=&\sum_idis(x,i)d_i\\ =&\sum_i(dis_x+dis_i-2dis_{\mathrm{lca}(x,i)})d_i\\ =&dis_x\sum_id_i+\sum_idis_i\cdot d_i-2\sum_idis_{\mathrm{lca(x,i)}}\cdot d_i \end{align*} \]

前面两项是好维护的,考虑如何维护最后一项。

枚举 \(\mathrm{lca}(x,i)\),有:

\[\large \begin{align*} &\sum_{i\in\mathrm{anc}(x)}dis_i(sz_i-sz_{son_i})\\ =&\sum_{i\in\mathrm{anc}(x)}(dis_i-dis_{fa_i})sz_i \end{align*} \]

在每个点维护 \(dis_i-dis_{fa_i}=w_i\) 即可。

实现上,用线段树维护每个点的 \(sz_i\)\(w_i\cdot sz_i\) 即可。

代码

#include <iostream>
#define int long long
const int N=1e5+5;
int n,Q,hed[N],tal[N<<1],wt[N<<1],nxt[N<<1],cnte,li[N],wa[N];
void de(int u,int v,int w) {tal[++cnte]=v,nxt[cnte]=hed[u],hed[u]=cnte,wt[cnte]=w;}
#define mid (lb+rb>>1)
struct Node
{
	int wsz,ddis,sum,tg;
	void mt(int t) {wsz+=t,sum+=ddis*t,tg+=t;}
} tr[N<<2];
void pd(int x)
{
	int &t=tr[x].tg;
	tr[x<<1].mt(t),tr[x<<1|1].mt(t),t=0;
}
void pu(int x)
{
	tr[x].wsz=std::max(tr[x<<1].wsz,tr[x<<1|1].wsz);
	tr[x].ddis=tr[x<<1].ddis+tr[x<<1|1].ddis;
	tr[x].sum=tr[x<<1].sum+tr[x<<1|1].sum;
}
void build(int x,int lb,int rb)
{
	if(lb==rb) return tr[x]={0,wa[li[lb]],0,0},void();
	build(x<<1,lb,mid),build(x<<1|1,mid+1,rb),pu(x);
}
void md(int x,int l,int r,int k,int lb,int rb)
{
	if(l<=lb&&rb<=r) return tr[x].mt(k);
	pd(x);
	if(l<=mid) md(x<<1,l,r,k,lb,mid);
	if(r>mid) md(x<<1|1,l,r,k,mid+1,rb);
	pu(x);
}
int qr(int x,int k,int lb,int rb)
{
	if(lb==rb) return li[lb];
	pd(x);
	if(tr[x<<1|1].wsz*2>=k) return qr(x<<1|1,k,mid+1,rb);
	return qr(x<<1,k,lb,mid);
}
int qsum(int x,int l,int r,int lb,int rb)
{
	if(l<=lb&&rb<=r) return tr[x].sum;
	pd(x);
	int ret=0;
	if(l<=mid) ret=qsum(x<<1,l,r,lb,mid);
	if(r>mid) ret+=qsum(x<<1|1,l,r,mid+1,rb);
	return ret;
}
#undef mid
int dfn[N],dis[N],dep[N],fa[N],son[N],sz[N],top[N],idx,sum,wsum;
void dfs1(int x)
{
	sz[x]=1;
	for(int i=hed[x],v;i;i=nxt[i]) if(!sz[v=tal[i]])
		fa[v]=x,dep[v]=dep[x]+1,dis[v]=dis[x]+wt[i],wa[v]=wt[i],
		dfs1(v),sz[x]+=sz[v],sz[v]>sz[son[x]]?son[x]=v:0;
}
void dfs2(int x,int tp)
{
	if(!x) return;
	dfn[x]=++idx,li[idx]=x,dfs2(son[x],top[x]=tp);
	for(int i=hed[x],v;i;i=nxt[i]) if(!top[v=tal[i]]) dfs2(v,v);
}
void md(int x,int k)
{
	while(x)
		md(1,dfn[top[x]],dfn[x],k,1,n),
		x=fa[top[x]];
}
int qr(int x)
{
	int ret=0;
	while(x) ret+=qsum(1,dfn[top[x]],dfn[x],1,n),x=fa[top[x]];
	return ret;
}
signed main()
{
	std::cin.tie(0)->sync_with_stdio(0);
	std::cin>>n>>Q;
	for(int i=1,u,v,w;i<n;i++) std::cin>>u>>v>>w,de(u,v,w),de(v,u,w);
	dfs1(1),dfs2(1,1),build(1,1,n);
	for(int i=1,x,y;i<=Q;i++)
	{
		std::cin>>x>>y;
		sum+=y,wsum+=dis[x]*y,md(x,y);
		int rt=qr(1,sum,1,n);
		std::cout<<dis[rt]*sum+wsum-2*qr(rt)<<'\n';
	}
}
posted @ 2025-12-06 16:06  整齐的艾萨克  阅读(0)  评论(0)    收藏  举报