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

幻想乡战略游戏

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

在游戏中,幽香可能在空地上增加或者减少一些军队。同时,幽香可以在一个空地上放置一个补给站。 如果补给站在点 \(u\) 上,并且空地 \(v\) 上有 \(d_v\) 个单位的军队,那么幽香每天就要花费 \(d_v \cdot \text{dist}(u,v)\) 的金钱来补给这些军队。由于幽香需要补给所有的军队,因此幽香总共就要花费为 \(\sum (d_v \cdot \text{dist}(u,v))\),其中\(1 \leq v \leq N\))的代价,\(\text{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\)的一个子节点,\(\text{sumd}_u\)\(\text{sumd}_v\)分别为\(u\)的子树内的\(d\)之和以及\(v\)的子树内的\(d\)之和,\(\text{len}(u,v)\)为边\((u,v)\)的长度。

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

\[\text{len}(u,v)\times(\text{sumd}_u-\text{sumd}_v-\text{sumd}_v) \]

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

\[2\times \text{sumd}_v>\text{sumd}_u \]

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

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

时间复杂度\(O(n\log n+Q20\log^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],\text{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){
	\text{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
		\text{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*(\text{sumd}[par[i]]-\text{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\n",query(root));
	}
	return 0;
}

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

时间复杂度\(O(n\log^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 \text{len}=Dist::calc(x,fa[i]);
		down[fa[i]]+=(int64)v*\text{len};
		up[i]+=(int64)v*\text{len};
	}
}
int64 calc(int x){
	int64 ans=down[x];
	for(int i=x;fa[i];i=fa[i]){
		int \text{len}=Dist::calc(x,fa[i]);
		ans+=down[fa[i]]-up[i]+(int64)(sum[fa[i]]-sum[i])*\text{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\n",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%的数据,满足\(n\leq 10^3,m\leq 10^3\)

对于60%的数据,满足\(m\leq 10^5\)

对于100%的数据,满足\(n\leq 5\times 10^4,m\leq 5\times 10^5,w\leq 10^3\)

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

时间限制:1S

空间限制:512MB

题解

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

那么用可持久化分块维护DFS序的前缀和,就能做到\(O(n\sqrt{n})+O(m\log 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;
}

posted on 2020-06-15 09:59  autoint  阅读(256)  评论(0编辑  收藏  举报

导航