P3224 [HNOI2012]永无乡(并查集 + splay启发式合并)

在这里插入图片描述


要维护的是一个连通块的有序序列,每次查询第k个值的编号,用 splay 很容易实现。用splay维护一个pair(权值,编号),查询即查询排名为 k 的值。

初始的时候对开 n 个根节点,分别插入 n 个值。
当添加一座桥(或输入一座桥),两个不同联通块所在的splay 用启发式合并,把每个点都插入即可。维护连通块用并查集,只需要维护连通块的根节点所代表的splay树

由于使用启发式合并,每个点最多被插入 log \log 次 ,复杂度为 n log 2 n n\log^2n ,如果不回收结点编号,空间复杂度将达到 n log n n \log n (回收结点编号莫名起码写挂了)


#include<bits/stdc++.h>
using namespace std;
#define pii pair<int,int>
#define fir first
#define sec second
const int maxn = 1e6 + 10;
const int inf = 0x3f3f3f3f;
int n,m,a[maxn],p[maxn],q;
char op[10];
vector<pii> tmp;
int find(int x) {
	return p[x] == x ? x : p[x] = find(p[x]);
}
int smaller(pii a,pii b) {
	if (a.fir != b.fir) return a.fir < b.fir;
	if (a.sec != b.sec) return a.sec < b.sec;
}
struct Splay_tree {
	int ch[maxn][2];		//ch[u][0] 表示 左二子,ch[u][1] 表示右儿子
	int f[maxn];			//当前节点的父节点 
	int sz[maxn]; 			//当前节点的子树的节点个数
	pii val[maxn];			//当前节点的权值和编号 
	int cnt[maxn];			//当前节点所表示的值的个数
	int root[maxn];			//根节点
	int tot;				//总节点的个数 
	queue<int> q;			//编号回收队列 ,防止空间复杂度达到 nlogn 
	inline bool get(int x){
    	return ch[f[x]][1]==x;
	}
	void init() {
		tot = 0;
		memset(root,0,sizeof root);
		memset(f,0,sizeof f);
		memset(sz,0,sizeof sz);
		memset(cnt,0,sizeof cnt);
		memset(ch,0,sizeof ch);
		//f[0] = ch[0][0] = ch[0][1] = sz[0] = cnt[0] = 0;
		//val[0] = pii(0,0);
	}
	void pushup(int rt) {							//维护 rt 的 sz
		if(rt) {
			sz[rt] = cnt[rt];
			if(ch[rt][0]) sz[rt] += sz[ch[rt][0]];
			if(ch[rt][1]) sz[rt] += sz[ch[rt][1]];
		}
	}
	void newnode(int rt,pii v,int fa) {		//新建节点 
		f[rt] = fa;
		val[rt] = v; cnt[rt] = sz[rt] = 1;
		ch[rt][0] = ch[rt][1] = 0;
	}
	void delnode(int &rt) {
		if(ch[rt][0]) delnode(ch[rt][0]);
		if(ch[rt][1]) delnode(ch[rt][1]);
		f[rt] = sz[rt] = cnt[rt] = 0;
		val[rt] = pii(0,0);
		ch[rt][0] = ch[rt][1] = 0;
		//q.push(rt);
		rt = 0;
	}
	void rotate(int x) {							//旋转操作,根据 x 在 f[x] 的哪一侧进行左旋和右旋 
	    int old = f[x], oldf = f[old];
	    int whichx = get(x);
	    ch[old][whichx] = ch[x][whichx ^ 1];
	    f[ch[old][whichx]] = old;
	    ch[x][whichx ^ 1] = old; f[old] = x;
	    f[x] = oldf;
	    if(oldf) ch[oldf][ch[oldf][1] == old] = x;
	    pushup(old); pushup(x);						//不要忘记更新size
	}
	void splay(int &rt,int x,int goal) {					//将 x 旋到 goal节点  下面
    	for (int fa = f[x]; fa != goal; rotate(x), fa = f[x])	//再把x翻上来
        	if (f[fa] != goal)						//如果fa非根,且x 和 fa是同一侧,那么先翻转fa,否则先翻转x 
            	rotate((get(x)==get(fa))?fa:x);
        if (goal == 0)
			rt = x;
	}
	int find(int rt,pii x) {						//查找 x,若 x 不存在返回的是 x 的前驱或者后继 (不一定就是前驱或者后继,两者都可能)					 
		int now = rt, father = 0;
		if(!now) return now;
		while(1) {
			if(val[now].fir == x.fir) return now;
			if(ch[now][0] && x.fir < val[now].fir) {
				father = now;
				now = ch[now][0];
			} else if(ch[now][1] && x.fir > val[now].fir) {
				father = now;
				now = ch[now][1];
			} else {
				return now;
			}
		}
	}
	int findx(int rt,int x) {								//查询第x小的节点 
		int now = rt;
		if (!now || x > sz[rt])  return 0;
		while(1) {
			if(ch[now][0] && sz[ch[now][0]] >= x) {
				now = ch[now][0];
			} else {
				int sum = cnt[now] + (ch[now][0] ? sz[ch[now][0]] : 0);
				if (x <= sum) return now;
				x -= sum, now = ch[now][1];
			}
		}
		return now;
	}
	void insert(int &rt,pii x) {				//把 x 插入到 splay中 
		int now = rt, father = 0,p = 0;
		if(rt == 0) {
			if (!q.empty()) p = q.front(), q.pop();
			else p = ++tot;
			newnode(p,x,0);
			rt = p;
			return ;
		}
		while(1) {
			if(val[now].fir == x.fir) {
				cnt[now] ++; sz[now]++;
				pushup(now); 
				splay(rt,now,0);
				break;
			}
			father = now;
			now = ch[now][x.fir > val[now].fir];
			if(!now) {											//该节点不存在,则新建节点,并把这个节点旋转上去作为根节点
				if (!q.empty()) p = q.front(), q.pop();
				else p = ++tot; 
				newnode(p,x,father);
				ch[father][x.fir > val[father].fir] = tot;
				f[p] = father;
				pushup(p); //pushup(father);
				splay(rt,p,0);
				break;
			}
			//printf("%d %d %d\n",rt,now,father);
		}
	}
	void dfs(int rt) {
		if(!rt) return ;
		dfs(ch[rt][0]);
		tmp.push_back(val[rt]);
		dfs(ch[rt][1]);
	}
}tree;
int main() {
	//freopen("out.txt","w",stdout);
	scanf("%d%d",&n,&m);
	tree.init();
	for (int i = 1; i <= n; i++) {
		p[i] = i,scanf("%d",&a[i]);
		tree.insert(tree.root[i],pii(a[i],i));
	}
	for (int i = 1,x,y; i <= m; i++) {
		scanf("%d%d",&x,&y);
		int fx = find(x);
		int fy = find(y);
		//printf("***%d %d %d %d\n",fx,fy,tree.sz[tree.root[fx]],tree.sz[tree.root[fy]]);
		if(fx == fy) continue;
		if(tree.sz[tree.root[fx]] < tree.sz[tree.root[fy]]) swap(fx,fy);
		tree.dfs(tree.root[fy]);
		tree.delnode(tree.root[fy]);
		for (auto it : tmp)
			tree.insert(tree.root[fx],it);
		p[fy] = fx;
		//printf("&&&%d %d %d %d %d\n",fx,fy,tree.sz[tree.root[fx]],tree.sz[tree.root[fy]]);
		tmp.clear();
	}
	scanf("%d",&q);
	while(q--) {
		int x,y; scanf("%s%d%d",op,&x,&y);
		if (op[0] == 'Q') {
			int fx = find(x);
			int res = tree.findx(tree.root[fx],y);
			if (!res) puts("-1");
			else printf("%d\n",tree.val[res].sec);
		} else {
			int fx = find(x), fy = find(y);
			if (fx != fy) {
				//printf("***%d %d %d %d\n",fx,fy,tree.sz[tree.root[fx]],tree.sz[tree.root[fy]]);
				if(tree.sz[tree.root[fx]] < tree.sz[tree.root[fy]]) swap(fx,fy);
				tree.dfs(tree.root[fy]);
				tree.delnode(tree.root[fy]);
				for (auto it : tmp)
					tree.insert(tree.root[fx],it);
				p[fy] = fx;
				tmp.clear();				
			}
		}
	}
	return 0;
}

发布了332 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_41997978/article/details/104189456