[WC2018] 即时战略

分享一下全局平衡二叉树的做法。

先讲下部分分。

\(n\le 100,T\le 10000\)

\(1\) 开始 DFS,对于当前 \(u\),枚举点 \(v\),如果 \(\text{explore}(u,v)\) 不为 \(fa_u\),则 \(v\)\(u\) 子结点,向 \(v\) 拓展即可。询问次数 \(O(n^2)\),可得 \(20pts\)

\(\texttt{datatype}=2\)

完全二叉树树高 \(O(\log n)\),子树大小之和为 \(O(n\log n)\)。给每个点开一个 vector 记录子树结点。对于 \(u\) 遍历子树结点 \(v\) 并询问 \(\text{explore}(u,v)=p\)。将 \(v\) 加入 \(p\) 的子树。最后 DFS 所有出现过的 \(p\) 即可。询问次数 \(O(n\log n)\),可得 \(35pts\)

\(\texttt{datatype}=1\)

次数是 \(n+\log n\) 的。当前已访问结点必然是一段链 \([l,r]\)随机取一个未访问点 \(u\),询问 \(\text{explore}(l,u)=p\)。如果 \(p\) 已被访问,则不断询问 \(\text{explore}(r,u)\) 拓展链;否则不断询问 \(\text{explore}(l,u)\)。注意更新 \(l,r\)。考虑 “\(p\) 已被访问”发生的次数是期望 \(O(\log n)\) 的。因为期望下 \(r\) 右边的链每次缩短一半。询问次数 \(n+\log n\),可得 \(65pts\)

正解

先随机一个 \(u\),拉出一条 \(1\to u\) 的链。然后再随机一个 \(v\),在链上二分确定它挂在 \(p\)。然后再拉出一条 \(p\to v\) 的链。这些链的深度都是严格递增的,不难想到重链剖分,这样可以做到 \(O(n\log^2n)\) 的询问次数。问题在于我们无法确定哪个是重儿子。如果你做过「EZEC-8」猜树 加强版,重子树的点总是比较多,我们随机一个点就可以把它所在的子树当作重子树

然而我们要做到 \(O(n\log n)\) 的询问次数。考虑优化在链上二分的过程,把它换成全局平衡二叉树就能做到 \(O(n\log n)\) 了。但是老问题又来了,我们不知道每个点的外挂子树大小。没关系,我们无需建出全局平衡二叉树,每次找到带权重心即可。

注意外挂子树大小 \(sz_i\) 的更新相当于每次跳重链并单点修改,每次要查询重链连续的一段 \([l,r]\),找到第一个位置 \(p\) 满足 \(\sum_{i=l}^psz_i>\frac 12\sum_{i=l}^r sz_i\),然后选取 \(p\) 为重心。

在每条重链上开一个 fhq treap 即可维护。时间复杂度 \(O(n\log^2n)\),询问次数 \(O(n\log n)\),可以通过。

还是比较好写的。细节的注释在代码里:

#include "rts.h"
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define ls u<<1
#define rs u<<1|1
#define mid ((l+r)>>1)
#define lp ch[x][0]
#define rp ch[x][1]
int n;
const int N=300005;
mt19937 rd(time(0));
namespace pool{//随机池
	int tr[4*N];
	void build(int l,int r,int u){
		tr[u]=r-l+1-(l==1);
		if(l==r) return;
		build(l,mid,ls),build(mid+1,r,rs);
	}
	void del(int l,int r,int u,int p){
		if(l==r) {tr[u]=0;return;}
		p<=mid?del(l,mid,ls,p):del(mid+1,r,rs,p);
		tr[u]=tr[ls]+tr[rs];
	}
	int kth(int l,int r,int u,int k){
		if(l==r) return l;
		if(k<=tr[ls]) return kth(l,mid,ls,k);
		return kth(mid+1,r,rs,k-tr[ls]);
	}
	void bk(int u){del(1,n,1,u);}//删除已访问点
	int get(){return kth(1,n,1,rd()%tr[1]+1);}//随机一个未访问点
}
namespace ghf{//链
	int l[N],r[N];
	void lk(int x,int y){r[x]=y,l[y]=x;}
	void solve(){
		int nl=1,nr=1;
		while(pool::tr[1]){
			int u=pool::get(),v;
			if((v=explore(nl,u))==r[nl]){
				while(nr!=u){v=explore(nr,u),lk(nr,v),nr=v,pool::bk(v);}
			}
			else{
				lk(v,nl),nl=v,pool::bk(v);
				while(nl!=u) v=explore(nl,u),lk(v,nl),nl=v,pool::bk(v);
			}
		}
	}
}
namespace thy{
	int fa[N],son[N],top[N],bot[N],goal,dep[N],lst;//fa[u]:父结点,son[u]:重儿子,top[u]:重链链顶,bot[u]:重链链底,goal:随机出来的 u,dep[u]:深度,lst:上一个点
	int idx,rt[2*N],tr[N],val[N],sz[N],p[N],ch[N][2];//idx:分配临时根,rt[u]:平衡树根结点,tr[u]:平衡树子树外挂子树大小和,val[u]:u 的外挂子树大小,sz[u]:平衡树子树结点数,p[u]:随机优先值,ch[u][0/1]:左右儿子
	void up(int x){//fhq treap
		tr[x]=tr[lp]+tr[rp]+val[x];
		sz[x]=sz[lp]+sz[rp]+1;
	}
	int merge(int x,int y){
		if(!x||!y) return x+y;
		if(p[x]<p[y]) return rp=merge(rp,y),up(x),x;
		return ch[y][0]=merge(x,ch[y][0]),up(y),y;
	}
	void split(int x,int &l,int &r,int k){//分裂为排名 [1,k],[k+1,n]
		if(!x){l=r=0;return;}
		if(sz[lp]+1<=k) l=x,split(rp,rp,r,k-sz[lp]-1);
		else r=x,split(lp,l,lp,k);
		up(x);
	}
	void add(int x,int k,int w){
		if(sz[lp]+1<k) add(rp,k-sz[lp]-1,w);
		else if(k<=sz[lp]) add(lp,k,w);
		else val[x]+=w;
		up(x);
	}
	int findrt(int x,int k){//找到第一个前缀和 >=k 的点
		if(k>tr[lp]+val[x]) return findrt(rp,k-(tr[lp]+val[x]));
		if(k<=tr[lp]) return findrt(lp,k);
		return x;
	}
	int GBT(int u,int up,int dn){//全局平衡二叉树(动态找重心)
		int g=findrt(rt[u],(tr[rt[u]]+1)/2),to=explore(g,goal),res;//g 为带权重心
		if(top[to]==u){//仍在重链里
			if(dep[to]>dep[g]){//往下找
				split(rt[u],rt[++idx],rt[u],dep[g]-dep[up]+1);
				res=GBT(u,son[g],dn),rt[u]=merge(rt[idx--],rt[u]);//回溯,把分裂出去的点合并回来
			}
			else{//往上找
				split(rt[u],rt[u],rt[++idx],dep[g]-dep[up]);
				res=GBT(u,up,fa[g]),rt[u]=merge(rt[u],rt[idx--]);
			}
		}
		else res=to,lst=g;
		return res;
	}
	void upd(int x,int w){//跳重链更新 val
		while(x){
			add(rt[top[x]],dep[x]-dep[top[x]]+1,w);
			x=fa[top[x]];
		}
	}
	void solve(){
		rt[top[1]=bot[1]=1]=1,idx=n;
		p[1]=rand(),sz[1]=val[1]=tr[1]=1;
		while(pool::tr[1]){
			goal=pool::get();int nw=1;
			while(nw=GBT(nw,nw,bot[nw]),top[nw]);
			fa[nw]=lst,dep[nw]=dep[lst]+1;
			val[nw]=tr[nw]=sz[nw]=1,p[nw]=rand(),rt[nw]=nw;
			if(top[lst]==bot[top[lst]]){//lst 还没有重儿子,把 nw 定为重儿子
				top[nw]=top[lst],bot[top[nw]]=nw,son[lst]=nw;
				rt[top[nw]]=merge(rt[top[nw]],rt[nw]);
			}
			else top[nw]=bot[nw]=nw;//另开一条重链
			pool::bk(nw);
			int w=1;
			while(nw!=goal){//拉出一条 nw->goal 的链
				lst=nw,nw=explore(nw,goal),w++,pool::bk(nw);
				dep[nw]=dep[lst]+1,fa[nw]=lst,son[lst]=nw,top[nw]=top[lst],bot[top[nw]]=nw;
				val[nw]=tr[nw]=sz[nw]=1,p[nw]=rand(),rt[nw]=nw;
				rt[top[nw]]=merge(rt[top[nw]],rt[nw]);
			}
			upd(fa[top[nw]],w);
		}
	}
}
void play(int num, int T, int dataType) {
	srand(time(0));
	n=num,pool::build(1,n,1);
	if(dataType==3) ghf::solve();//链的限制比较紧,特判
	else thy::solve();
}
posted on 2025-10-04 19:47  xinjingzhu  阅读(14)  评论(0)    收藏  举报