• ZJOI2015 幻想乡战略游戏 和 SCOI2019 找重心


    幻想乡战略游戏

    傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来,更别说和别人打仗了。 在打仗之前,幽香现在面临一个非常基本的管理问题需要解决。 整个地图是一个树结构,一共有 (n) 块空地,这些空地被 (n-1) 条带权边连接起来,使得每两个点之间有一条唯一的路径将它们连接起来。

    在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点 (u) 上,并且空地 (v) 上有 (d_v) 个单位的军队,那么幽香每天就要花费 (d_v cdot ext{dist}(u,v)) 的金钱来补给这些军队。由于幽香需要补给所有的军队,因此幽香总共就要花费为 (sum (d_v cdot ext{dist}(u,v))),其中(1 leq v leq N))的代价,( ext{dist}(u,v)) 表示 (u)(v) 在树上的距离(唯一路径的权和)。 因为游戏的规定,幽香只能选择一个空地作为补给站。在游戏的过程中,幽香可能会在某些空地上制造一些军队,也可能会减少某些空地上的军队,进行了这样的操作以后,出于经济上的考虑,幽香往往可以移动他的补给站从而省一些钱。但是由于这个游戏的地图是在太大了,幽香无法轻易的进行最优的安排,你能帮帮她吗? 你可以假定一开始所有空地上都没有军队。

    对于所有数据,(1 leq c leq 1000, 0 leq |e| leq 1000, n leq 10^5, Q leq 10^5),并保证所有节点的度数小于等于 (20).

    分析

    考虑答案的形式,发现跟带权重心非常类似。

    假设当前补给站为(u),并强制以(u)为根,(v)(u)的一个子节点,( ext{sumd}_u)( ext{sumd}_v)分别为(u)的子树内的(d)之和以及(v)的子树内的(d)之和,( ext{len}(u,v))为边((u,v))的长度。

    如果将补给站迁移到点(v),那么(v)的子树内的点到补给站的距离减少了( ext{len}(u,v)),其他的点到补给站的距离增加了( ext{len}(u,v))。也就是说,补给站迁移到点(v)时,代价的增量为:

    [ ext{len}(u,v) imes( ext{sumd}_u- ext{sumd}_v- ext{sumd}_v) ]

    整理一下,得出性质:(u)为根,(v)(u)的子节点,补给站在(v)(u)优,当且仅当:

    [2 imes ext{sumd}_v> ext{sumd}_u ]

    显然满足条件的(v)最多只有一个。这时候,如果没有满足条件的(v),则(u)为最优位置。否则最优位置在(v)子树内

    一个一个跳肯定不行,所以考虑使用点分树加速跳跃过程。具体而言,如果发现(u ightarrow v)更优的话,那么就递归计算(v)子树中的重心,即(u)在点分树上的儿子节点。运用换根DP的知识我们可以维护点分树上面每个节点子树的答案并且做到(O(log n))查询每个节点换根后的答案。

    时间复杂度(O(nlog n+Q20log^2 n))

    动态点分治的作用:在点分治的过程中,一般我们面对的问题都是静态的。如果涉及到修改这类的操作,我们就希望找到我们是如何处理到当前的修改点的,换而言之,我们希望记录下点分治的过程,这样可以通过爬点分树等操作消除影响。

    co int N=2e5+1;
    int n,m;
    namespace T{ // original tree
    	vector<pii> e[N];
    	int lg[N*2],st[N*2][18],dis[N],pos[N],dfn;
    	void dfs(int u,int fa){
    		st[pos[u]=++dfn][0]=dis[u];
    		for(int i=0,v;i<e[u].size();++i){
    			if((v=e[u][i].first)==fa) continue;
    			dis[v]=dis[u]+e[u][i].second,dfs(v,u);
    			st[++dfn][0]=dis[u];
    		}
    	}
    	void init(){
    		lg[0]=-1;
    		for(int i=1;i<=n<<1;++i) lg[i]=lg[i>>1]+1;
    		dfs(1,0),assert(dfn==2*n-1);
    		for(int j=1;1<<j<=dfn;++j)
    			for(int i=1;i+(1<<j)-1<=dfn;++i)
    				st[i][j]=min(st[i][j-1],st[i+(1<<j-1)][j-1]);
    	}
    	int get_dis(int u,int v){
    		if(pos[u]>pos[v]) swap(u,v);
    		int k=lg[pos[v]-pos[u]+1];
    		return dis[u]+dis[v]-2*min(st[pos[u]][k],st[pos[v]-(1<<k)+1][k]);
    	}
    }
    int vis[N],sum,root,siz[N],f[N],par[N];
    ll down[N],up[N],	ext{sumd}[N];
    vector<pii> g[N];
    void get_root(int u,int fa){
    	siz[u]=1,f[u]=0;
    	for(int i=0,v;i<T::e[u].size();++i){
    		if(vis[v=T::e[u][i].first]||v==fa) continue;
    		get_root(v,u);
    		siz[u]+=siz[v],f[u]=max(f[u],siz[v]);
    	}
    	f[u]=max(f[u],sum-siz[u]);
    	if(f[u]<f[root]) root=u;
    }
    void work(int u,int fa){
    	vis[u]=1,par[u]=fa;
    	for(int i=0,v;i<T::e[u].size();++i){
    		if(vis[v=T::e[u][i].first]) continue;
    		sum=siz[v],root=0,get_root(v,0);
    		g[u].push_back(pii(root,v));
    		work(root,u);
    	}
    }
    void ins(int u,int val){
    		ext{sumd}[u]+=val;
    	for(int i=u;par[i];i=par[i]){
    		int dist=T::get_dis(par[i],u);
    		down[par[i]]+=(ll)dist*val; // underneath ans
    		up[i]+=(ll)dist*val; // upward transfer
    			ext{sumd}[par[i]]+=val;
    	}
    }
    ll calc(int u){
    	ll ans=down[u];
    	for(int i=u;par[i];i=par[i]){
    		int dist=T::get_dis(par[i],u);
    		ans+=down[par[i]]-up[i]+dist*(	ext{sumd}[par[i]]-	ext{sumd}[i]);
    	}
    	return ans;
    }
    ll query(int u){
    	ll ans=calc(u);
    	for(int i=0;i<g[u].size();++i)
    		if(calc(g[u][i].second)<ans)
    			return query(g[u][i].first);
    	return ans;
    }
    int main(){
    	read(n),read(m);
    	for(int i=1,u,v,w;i<n;++i){
    		read(u),read(v),read(w);
    		T::e[u].push_back(pii(v,w)),T::e[v].push_back(pii(u,w));
    	}
    	T::init();
    	sum=n,f[0]=n,get_root(1,0);
    	int tmp=root;work(root,0),root=tmp;
    	for(int u,e;m--;){
    		read(u),read(e),ins(u,e);
    		printf("%lld
    ",query(root));
    	}
    	return 0;
    }
    

    将树二度化之后就不需要点度数(leq 20)的性质了。

    时间复杂度(O(nlog^2 n))

    这样做的本质其实就是二分。

    CO int N=4e5+10;
    struct edge {int y,w;};
    vector<edge> to[N];
    
    namespace Rebuild{
    	vector<edge> to[N];
    	
    	void dfs(int x,int fa){
    		for(int i=0;i<(int)to[x].size();++i){
    			int y=to[x][i].y;
    			if(y==fa){
    				to[x].erase(to[x].begin()+i),--i;
    				continue;
    			}
    			dfs(y,x);
    		}
    	}
    	int main(int n){
    		for(int i=1;i<n;++i){
    			int x=read<int>(),y=read<int>(),w=read<int>();
    			to[x].push_back({y,w}),to[y].push_back({x,w});
    		}
    		dfs(1,0);
    		for(int x=1;x<=n;++x){
    			if(to[x].size()<=2){
    				for(CO edge&e:to[x])
    					::to[x].push_back({e.y,e.w}),::to[e.y].push_back({x,e.w});
    				continue;
    			}
    			int l=++n,r=++n;
    			::to[x].push_back({l,0}),::to[l].push_back({x,0});
    			::to[x].push_back({r,0}),::to[r].push_back({x,0});
    			to[l].assign(to[x].begin(),to[x].begin()+to[x].size()/2);
    			to[r].assign(to[x].begin()+to[x].size()/2,to[x].end());
    		}
    		return n;
    	}
    }
    
    namespace Dist{
    	int dep[N],pos[N],tim;
    	int st[2*N][20],lg[2*N];
    	
    	void dfs(int x,int fa){
    		pos[x]=++tim,st[tim][0]=dep[x];
    		for(CO edge&e:to[x])if(e.y!=fa){
    			dep[e.y]=dep[x]+e.w;
    			dfs(e.y,x);
    			st[++tim][0]=dep[x];
    		}
    	}
    	void main(){
    		dfs(1,0);
    		lg[0]=-1;
    		for(int i=1;i<=tim;++i) lg[i]=lg[i>>1]+1;
    		for(int k=1;k<=lg[tim];++k)for(int i=1;i+(1<<k)-1<=tim;++i)
    			st[i][k]=min(st[i][k-1],st[i+(1<<(k-1))][k-1]);
    	}
    	IN int calc(int x,int y){
    		if(pos[x]>pos[y]) swap(x,y);
    		int k=lg[pos[y]-pos[x]+1];
    		return dep[x]+dep[y]-2*min(st[pos[x]][k],st[pos[y]-(1<<k)+1][k]);
    	}
    }
    
    namespace Seg{
    	int pos[N],tim,lst[N];
    	
    	void dfs(int x,int fa){
    		pos[x]=++tim;
    		for(CO edge&e:to[x])if(e.y!=fa) dfs(e.y,x);
    		lst[x]=tim;
    	}
    	void main(){
    		dfs(1,0);
    	}
    	
    	int sum[4*N];
    	#define lc (x<<1)
    	#define rc (x<<1|1)
    	#define mid ((l+r)>>1)
    	void insert(int x,int l,int r,int p,int v){
    		sum[x]+=v;
    		if(l==r) return;
    		if(p<=mid) insert(lc,l,mid,p,v);
    		else insert(rc,mid+1,r,p,v);
    	}
    	int query(int x,int l,int r,int ql,int qr){
    		if(ql<=l and r<=qr) return sum[x];
    		if(qr<=mid) return query(lc,l,mid,ql,qr);
    		if(ql>mid) return query(rc,mid+1,r,ql,qr);
    		return query(lc,l,mid,ql,qr)+query(rc,mid+1,r,ql,qr);
    	}
    	#undef lc
    	#undef rc
    	#undef mid
    	IN void insert(int x,int v){
    		insert(1,1,tim,pos[x],v);
    	}
    	IN int query(int x,int y){
    		if(pos[x]>pos[y]) return query(1,1,tim,pos[x],lst[x]);
    		else return sum[1]-query(1,1,tim,pos[y],lst[y]);
    	}
    }
    
    int vis[N],siz[N],all;
    pair<int,int> root;
    int fa[N];
    vector<edge> ch[N];
    	
    void find_root(int x,int fa){
    	siz[x]=1;
    	pair<int,int> ans={0,x};
    	for(CO edge&e:to[x])if(!vis[e.y] and e.y!=fa){
    		find_root(e.y,x);
    		siz[x]+=siz[e.y];
    		ans.first=max(ans.first,siz[e.y]);
    	}
    	ans.first=max(ans.first,all-siz[x]);
    	root=min(root,ans);
    }
    void build(int x){
    	vis[x]=1;
    	int old=all;
    	for(CO edge&e:to[x])if(!vis[e.y]){
    		root={all=siz[e.y]<siz[x]?siz[e.y]:old-siz[x],0},find_root(e.y,x);
    		fa[root.second]=x,ch[x].push_back({root.second,e.y});
    		build(root.second);
    	}
    }
    	
    int cen,sum[N];
    int64 down[N],up[N];
    
    void insert(int x,int v){
    	sum[x]+=v;
    	for(int i=x;fa[i];i=fa[i]){
    		sum[fa[i]]+=v;
    		int 	ext{len}=Dist::calc(x,fa[i]);
    		down[fa[i]]+=(int64)v*	ext{len};
    		up[i]+=(int64)v*	ext{len};
    	}
    }
    int64 calc(int x){
    	int64 ans=down[x];
    	for(int i=x;fa[i];i=fa[i]){
    		int 	ext{len}=Dist::calc(x,fa[i]);
    		ans+=down[fa[i]]-up[i]+(int64)(sum[fa[i]]-sum[i])*	ext{len};
    	}
    	return ans;
    }
    int64 query(int x){
    	for(CO edge&e:ch[x])
    		if(2*Seg::query(e.w,x)>sum[cen]) return query(e.y);
    	return calc(x);
    }
    
    int main(){
    	int n=read<int>(),m=read<int>();
    	n=Rebuild::main(n);
    	Dist::main();
    	Seg::main();
    	root={all=n,0},find_root(1,0);
    	cen=root.second;
    	build(root.second);
    	while(m--){
    		int x=read<int>(),v=read<int>();
    		Seg::insert(x,v);
    		insert(x,v);
    		printf("%lld
    ",query(cen));
    	}
    	return 0;
    }
    

    找重心

    JKLover种了一棵(n)个点的有边权的树。他每次会拍拍脑袋想出一个区间([l,r])(长度为奇数),然后心算出编号在这个区间内的点的带权重心。为了检验他算的对不对,他请你写程序来验算。

    输入格式

    第一行一个整数(n)表示树的点数。

    接下来(n-1)行,每行三个整数(x,y,w)表示一条边。

    下一行一个整数(m)表示询问的次数。

    接下来(m)行,每行两个整数(l,r)表示一次询问的区间。

    输出格式

    输出(m)行,每行一个整数表示答案。

    样例

    5
    5 1 3
    1 2 5
    2 4 5
    4 3 1
    5
    2 4
    3 3
    1 5
    1 3
    2 4
    
    4
    3
    2
    2
    4
    

    数据范围与提示

    对于15%的数据,满足(nleq 10^3,mleq 10^3)

    对于60%的数据,满足(mleq 10^5)

    对于100%的数据,满足(nleq 5 imes 10^4,mleq 5 imes 10^5,wleq 10^3)

    请使用较快的输入输出方式。

    时间限制:1S

    空间限制:512MB

    题解

    注意这道题只需要求出重心,而不需要求出重心到([l,r])的距离和,所以要简单很多。

    那么用可持久化分块维护DFS序的前缀和,就能做到(O(nsqrt{n})+O(mlog n))

    可持久化分块可以把信息存到边上,比较好实现一些。

    区间长度为奇数保证了答案唯一。

    CO int N=1e5+10;
    struct edge {int y,w;};
    vector<edge> to[N];
    int from[N];
    
    namespace Rebuild{
    	vector<edge> to[N];
    	
    	void dfs(int x,int fa){
    		for(int i=0;i<(int)to[x].size();++i){
    			int y=to[x][i].y;
    			if(y==fa){
    				to[x].erase(to[x].begin()+i),--i;
    				continue;
    			}
    			dfs(y,x);
    		}
    	}
    	int main(int n){
    		for(int i=1;i<n;++i){
    			int x=read<int>(),y=read<int>(),w=read<int>();
    			to[x].push_back({y,w}),to[y].push_back({x,w});
    		}
    		dfs(1,0);
    		for(int x=1;x<=n;++x) from[x]=x;
    		for(int x=1;x<=n;++x){
    			if(to[x].size()<=2){
    				for(CO edge&e:to[x])
    					::to[x].push_back({e.y,e.w}),::to[e.y].push_back({x,e.w});
    				continue;
    			}
    			int l=++n,r=++n;
    			from[l]=from[r]=from[x];
    			::to[x].push_back({l,0}),::to[l].push_back({x,0});
    			::to[x].push_back({r,0}),::to[r].push_back({x,0});
    			to[l].assign(to[x].begin(),to[x].begin()+to[x].size()/2);
    			to[r].assign(to[x].begin()+to[x].size()/2,to[x].end());
    		}
    		return n;
    	}
    }
    
    namespace Block{
    	int pos[N],tim,idx[N],lst[N];
    	
    	void dfs(int x,int fa){
    		pos[x]=++tim,idx[tim]=x;
    		for(CO edge&e:to[x])if(e.y!=fa) dfs(e.y,x);
    		lst[x]=tim;
    	}
    	
    	CO int B=316;
    	int root[N],tot;
    	int ch[N][B+10],sum[N][B+10];
    	
    	void main(int n){
    		dfs(1,0);
    		for(int i=1;i<=n;++i){
    			int p=pos[i];
    			int&x=root[i]=root[i-1];
    			++tot,copy(ch[x]+1,ch[x]+(tim+B-1)/B+1,ch[tot]+1);
    			copy(sum[x]+1,sum[x]+(tim+B-1)/B+1,sum[tot]+1),x=tot;
    			for(int i=(p+B-1)/B+1;i<=(tim+B-1)/B;++i) ++sum[x][i];
    			int&y=ch[x][(p+B-1)/B];
    			++tot,copy(ch[y]+1,ch[y]+B+1,ch[tot]+1);
    			copy(sum[y]+1,sum[y]+B+1,sum[tot]+1),y=tot;
    			for(int i=p-(p-1)/B*B;i<=B;++i) ++sum[y][i];
    		}
    	}
    	IN int query(int i,int p){
    		int x=root[i];
    		int ans=sum[x][(p+B-1)/B];
    		x=ch[x][(p+B-1)/B];
    		ans+=sum[x][p-(p-1)/B*B];
    		return ans;
    	}
    	IN int query(int l,int r,int x,int y){
    		if(pos[x]>pos[y]) return query(r,lst[x])-query(r,pos[x]-1)-(query(l-1,lst[x])-query(l-1,pos[x]-1));
    		else return r-l+1-(query(r,lst[y])-query(r,pos[y]-1)-(query(l-1,lst[y])-query(l-1,pos[y]-1)));
    	}
    }
    
    int vis[N],siz[N],all;
    pair<int,int> root;
    vector<edge> ch[N];
    
    void find_root(int x,int fa){
    	siz[x]=1;
    	pair<int,int> ans={0,x};
    	for(CO edge&e:to[x])if(!vis[e.y] and e.y!=fa){
    		find_root(e.y,x);
    		siz[x]+=siz[e.y];
    		ans.first=max(ans.first,siz[e.y]);
    	}
    	ans.first=max(ans.first,all-siz[x]);
    	root=min(root,ans);
    }
    void build(int x){
    	vis[x]=1;
    	int old=all;
    	for(CO edge&e:to[x])if(!vis[e.y]){
    		root={all=siz[e.y]<siz[x]?siz[e.y]:old-siz[x],0},find_root(e.y,x);
    		ch[x].push_back({root.second,e.y});
    		build(root.second);
    	}
    }
    int query(int x,int l,int r){
    	for(CO edge&e:ch[x])
    		if(2*Block::query(l,r,e.w,x)>r-l+1) return query(e.y,l,r);
    	return x;
    }
    
    int main(){
    	freopen("centroid.in","r",stdin),freopen("centroid.out","w",stdout);
    	int n=read<int>();
    	all=Rebuild::main(n);
    	Block::main(n);
    	root={all,0},find_root(1,0);
    	int cen=root.second;
    	build(root.second);
    	for(int m=read<int>();m--;){
    		int l=read<int>(),r=read<int>();
    		writeln(from[query(cen,l,r)]);
    	}
    	return 0;
    }
    
  • 相关阅读:
    js原生图片拼图Demo
    display:inline-block在ie7下的解决办法
    Apollo 配置中心部署注意事项
    chrony 时间同步配置
    IPv6基础介绍
    Rabbitmq 报错 nodedown
    Maven 私服你应该不陌生吧,可你会用 Artifactory 搭建吗?
    你 MySQL 中重复数据多吗,教你一招优雅的处理掉它们!
    MySQL 数据库的基本使用
    自建 yum 源
  • 原文地址:https://www.cnblogs.com/autoint/p/10685449.html
Copyright © 2020-2023  润新知