[ZJOI2015]幻想乡战略游戏 题解

题目链接:\(luogu\)


声明变量:

\(tr1/tr2\):原树/点分树,用链式前向星维护

求链长(包括求 \(lca\)

\(a_i\):原树欧拉序
\(st_{i,j}\)\(RMQ\) 数组
\(dist_i\):在原树中点到根的距离
\(dep_i\):在原树中点的深度
\(lg_i\):预处理 \(\log_2i\)
\(dfn_i\):点第一次在欧拉序上出现位置
\(dis(x,y)\):在原树中,从 \(x\)\(y\) 的路径长

构造点分树

\(rt\):原树重心
\(Rt\):子树重心
\(sz_i\):在原树中的子树大小
\(mx_i\):在原树中,若以 \(i\) 为根,最大的子树大小
\(vis_i\):是否经过(打标记)
\(fa_i\):点分树上节点父亲

点分树维护信息

\(d_i\):点 \(i\) 士兵数
\(sm_i\):子树 \(d\) 值和
\(smp_i\):若 \(i=rt\),为子树内所有点 \(v\)\(dis(i,v)\times d_v\) 之和
$\ \ \ \ \ \ \ \ \ \ \ $ 否则,为子树内所有点 \(v\)\(dis(fa_i,v)\times d_v\) 之和

下文会借用 \(siz_i\),表示原树子树 \(d\) 值和


乍一看没啥想法,和点分树关系也不大。但是我们可以发现一个性质:

设当前答案在 \(u\) 子树内,\(u\) 在点分树上有一子节点 \(v\),满足\(sm_{rt}<2sm_v\),那么答案在 \(v\) 子树内,否则答案为 \(u\)

设根节点答案为 \(w\),则将站点移到其原树子节点 \(v'\) 时,答案变为 \(w-div(rt,v')\times(siz_{rt}-2siz_{v'})\)
那么,若从根节点移动到点 \(v\),答案变为 \(w-div(rt,v)\times(siz_{rt}-2siz_v)\)。易得:当 \(2siz_v>siz_{rt}\) 时,答案更优。
这样,从点分树上一节点,移动到其子节点,同样可以使用 \(2sm_v>sm_{rt}\) 判定是否更优。


利用这个性质,可以很快判断出是否继续下行,因度数不大于 \(20\),点分树高不超过 \(\log n\),所以修改和寻点解决(这一部分,时间复杂度 \(O(n\log n)\))。
利用 \(smp\),我们可以快速求出子树内的答案,每往下走一层,就将原先处理好的其他部分 \(\times dis(u,v)+\) 自己和自己其他子树的贡献。


具体看代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
int rd(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') ch=='-'&&(f=-1),ch=getchar();
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}const int N=100005,L=22;ll d[N],sm[N],smp[N];
int n,q,rt,sz[N],mx[N],dep[N],dist[N],vis[N];
int ou,lg[N*2],st[N*2][L],dfn[N],fa[N],Rt,a[N*2];
struct tree{
	int m,h[N],to[N*2],nxt[N*2],w[N*2];
	void ad(int u,int v,int x){
		to[++m]=v;w[m]=x;
		nxt[m]=h[u];h[u]=m;
	}
}tr1,tr2;void dfs_ou(int x,int f){
	dep[x]=dep[f]+1;dfn[x]=++ou;a[ou]=x;
	for(int i=tr1.h[x];i;i=tr1.nxt[i]){
		int y=tr1.to[i];if(y==f) continue;
		dist[y]=dist[x]+tr1.w[i];
		dfs_ou(y,x);a[++ou]=x;
	}
}void rmq_lca(){
	lg[0]=-1;for(int i=1;i<=ou;i++) st[i][0]=a[i];
	for(int i=1;i<=N*2;i++) lg[i]=lg[i/2]+1;
	for(int j=1;j<19;j++)
		for(int i=1;i<=ou-(1<<j)+1;i++){
			int x=st[i][j-1],y=st[i+(1<<(j-1))][j-1];
			st[i][j]=dep[x]<dep[y]?x:y;
		}
}int dis(int x,int y){
	int l=dfn[x],r=dfn[y];if(l>r) swap(l,r);
	int k=lg[r-l+1],s=st[l][k],e=st[r-(1<<k)+1][k];
	int lca=s;if(dep[s]>dep[e]) lca=e;
	return dist[x]+dist[y]-dist[lca]*2;
}void dfs_sz(int x,int f){
	sz[x]=1;for(int i=tr1.h[x];i;i=tr1.nxt[i])
		if(tr1.to[i]!=f&&!vis[tr1.to[i]])
			dfs_sz(tr1.to[i],x),sz[x]+=sz[tr1.to[i]];
}void dfs_rt(int z,int x,int f){
	mx[x]=sz[z]-sz[x];
	for(int i=tr1.h[x];i;i=tr1.nxt[i])
		if(tr1.to[i]!=f&&!vis[tr1.to[i]])
			mx[x]=max(mx[x],sz[tr1.to[i]]);
	if(mx[x]<mx[Rt]) Rt=x;for(int i=tr1.h[x];i;i=tr1.nxt[i])
		if(tr1.to[i]!=f&&!vis[tr1.to[i]]) dfs_rt(z,tr1.to[i],x);
}int dfs_mk(int x,int f){
	dfs_sz(x,0);Rt=x;dfs_rt(x,x,0);
	if(!rt) rt=Rt;vis[Rt]=1;fa[Rt]=f;int t=Rt;
	for(int i=tr1.h[Rt];i;i=tr1.nxt[i]){
		if(vis[tr1.to[i]]) continue;
		int w=dfs_mk(tr1.to[i],t);tr2.ad(t,w,tr1.to[i]);
	}return t;
}void change(int x,ll k){
	int y=x;d[x]+=k;while(x)
		sm[x]+=k,smp[x]+=k*dis(y,fa[x]?fa[x]:x),x=fa[x];
}ll qdt(int u,int w,int x){
	ll r1=0,r2=d[u];
	for(int i=tr2.h[u],v;i;i=tr2.nxt[i])
		if((v=tr2.to[i])!=w) r1+=smp[v],r2+=sm[v];
	d[x]+=r2;return r1+r2*dis(u,x);
}ll que(ll as,int x){
	for(int i=tr2.h[x],v;i;i=tr2.nxt[i])
		if(sm[(v=tr2.to[i])]*2>sm[x]){
			ll w=d[tr2.w[i]],tp,dt=qdt(x,v,tr2.w[i]);
			tp=d[tr2.w[i]]-w;for(int j=tr2.w[i];j!=x;j=fa[j])
				sm[j]+=tp,smp[j]+=tp*dis(tr2.w[i],fa[j]?fa[j]:j);
			ll anss=0;for(int j=tr2.h[v];j;j=tr2.nxt[j])
				anss+=smp[tr2.to[j]];as=dt+que(anss,v);
			for(int j=tr2.w[i];j!=x;j=fa[j])
				sm[j]-=tp,smp[j]-=tp*dis(tr2.w[i],fa[j]?fa[j]:j);
			d[tr2.w[i]]=w;return as;
		}return as;
}signed main(){
	n=rd();q=rd();
	for(int i=1,x,y,z;i<n;i++)
		x=rd(),y=rd(),z=rd(),tr1.ad(x,y,z),tr1.ad(y,x,z);
	dfs_ou(1,0);rmq_lca();dfs_mk(1,0);
	while(q--){
		int x=rd(),y=rd();change(x,y);
		printf("%lld\n",que(smp[rt],rt));
	}return 0;
}//Kaká
posted @ 2024-02-03 21:07  长安一片月_22  阅读(22)  评论(0编辑  收藏  举报