[ZJOI2015]幻想乡战略游戏
作者:互联网
https://www.luogu.org/problemnew/show/P3345
动态点分治
考虑到带权重心一定在当前点到距离与权重总和更小的方向上(没有则当前点为重心),并且这个方向是唯一的,因此可以每次修改都这样移动,把重心找出。
然而直接移动就是直接暴力,需要更优雅的做法。
考虑在子树中移动,可以使用动态点分治的点分树,这样树高只有logn,就可以在每层之间移动重心。
那么只需要想办法求出所有带权点到某个点的距离,并支持修改即可。
由于一个子树以外的点到子树中某点距离可以看作先到根,再从根到该点,所以可以考虑容斥的方法。
然后维护两个数组即可。由于保证了每个点的度数不超过20,因此每次暴力移动重心,直到所有子节点都比它大为止,此时当前点就是重心。
时间复杂度O(20nlog2n)
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> using namespace std; typedef long long ll; const int N=100005; int n,q,i,j,k,l,x,y,h[2*N],lg[2*N],mnpt[2*N][20],num,st[N],ed[N],head[N],adj[N*2],nxt[N*2],len[N*2],sz[N],rt,w[N],tot,Head[N],Adj[N*2],Nxt[N*2],Fa[N],dep[N],P[N][20],Dep[N],to[N*2]; ll sum[N],sumx[N],sumfa[N],dis[N]; bool v[N]; inline void Read(int &x) { char c; int t; while((c=getchar())!='-'&&(c<'0'||c>'9')); if(c=='-') x=0,t=-1; else x=c-'0',t=1; while((c=getchar())>='0'&&c<='9') x=x*10+c-'0'; x*=t; } void Dfs(int x,int dad) { sz[x]=1; w[x]=-1; for(int y=head[x];y;y=nxt[y]) if(adj[y]!=dad&&!v[adj[y]]) { Dfs(adj[y],x); sz[x]+=sz[adj[y]]; w[x]=max(w[x],sz[adj[y]]); } w[x]=max(w[x],tot-sz[x]); if(rt==-1||w[x]<w[rt]) rt=x; } void addedge(int u,int v) { Adj[++l]=v; Nxt[l]=Head[u]; Head[u]=l; Fa[v]=u; } int dfs(int x,int size) { tot=size,rt=-1; Dfs(x,-1); v[rt]=true; int rtn=rt; for(int y=head[rt];y;y=nxt[y]) if(!v[adj[y]]) addedge(rtn,to[y]=dfs(adj[y],sz[adj[y]])); return rtn; } void work(int x,int dad) { h[++num]=x; st[x]=ed[x]=num; for(int y=head[x];y;y=nxt[y]) if(adj[y]!=dad) { dep[adj[y]]=dep[x]+1; dis[adj[y]]=dis[x]+len[y]; work(adj[y],x); h[++num]=x; ed[x]=num; } } int lca(int a,int b) { if(st[a]>ed[b]) swap(a,b); int i=lg[ed[b]-st[a]+1]; if(dep[mnpt[st[a]][i]]<dep[mnpt[ed[b]-(1<<i)+1][i]]) return mnpt[st[a]][i]; else return mnpt[ed[b]-(1<<i)+1][i]; } ll dist(int x,int y) { return dis[x]+dis[y]-2*dis[lca(x,y)]; } ll dzx(int x) { ll rtn=sumx[x]; int y=x; while(Fa[y]) { rtn=rtn+sumx[Fa[y]]-sumfa[y]+(sum[Fa[y]]-sum[y])*dist(Fa[y],x);//容斥 y=Fa[y]; } return rtn; } void Work(int x) { for(int y=Head[x];y;y=Nxt[y]) { Dep[Adj[y]]=Dep[x]+1; P[Adj[y]][0]=x; Work(Adj[y]); } } int main() { Read(n);Read(q); for(i=1;i<n;++i) { Read(j);Read(k);Read(len[i*2]); adj[i*2-1]=k; nxt[i*2-1]=head[j]; head[j]=i*2-1; adj[i*2]=j; nxt[i*2]=head[k]; head[k]=i*2; len[i*2-1]=len[i*2]; } rt=dfs(1,n); work(1,-1); for(i=1;i<=num;++i) { mnpt[i][0]=h[i]; if(i==1) lg[i]=0; else if(i==(1<<(lg[i-1]+1))) lg[i]=lg[i-1]+1; else lg[i]=lg[i-1]; } for(i=1;(1<<i)<=num;++i) for(j=1;j+(1<<i)-1<=num;++j) if(dep[mnpt[j][i-1]]<dep[mnpt[j+(1<<(i-1))][i-1]]) mnpt[j][i]=mnpt[j][i-1]; else mnpt[j][i]=mnpt[j+(1<<(i-1))][i-1]; Work(1); for(i=1;(1<<i)<n;++i) for(j=1;j<=n;++j) if(P[j][i-1]) P[j][i]=P[P[j][i-1]][i-1]; while(q--) { Read(i);Read(j); k=i; while(k) { sum[k]+=j; sumx[k]+=1ll*dist(k,i)*j; if(Fa[k]) sumfa[k]+=1ll*dist(Fa[k],i)*j; k=Fa[k]; } x=rt; while(1) { ll phh=dzx(x); for(y=head[x];y;y=nxt[y])//题目保证度数不超过20,所以枚举出边的复杂度正确 { if(dzx(adj[y])<phh) { x=to[y]; y=1; break; } } if(!y) break; } printf("%lld\n",dzx(x)); } return 0; }
标签:幻想,移动,20,游戏,重心,int,st,ZJOI2015,include 来源: https://www.cnblogs.com/pthws/p/11219442.html