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;
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM