P3391 【模板】文艺平衡树 (Splay区间操作,Splay区间翻转模板)

在这里插入图片描述


用 BST 可以维护序列的区间翻转,首先要以下标为排序关键字维护这个序列。 在 BST 上中序遍历就可以得到原序列。 而区间翻转操作对应的就是交换一个节点的两棵子树。

建树
不需要一个一个插入 ,可以像线段树建树那样分治建树,常数更小。

	void build(int &rt,int l,int r,int fa) {
		if (l > r) return ;
		int mid = l + r >> 1;
		rt = mid; newnode(rt,rt,fa);
		build(ch[rt][0],l,mid - 1,rt);
		build(ch[rt][1],mid + 1,r,rt);
		pushup(rt);
	}

区间操作:
维护对序列的区间操作:对区间 [ l , r ] [l,r] 进行操作时,先将第 l 1 l - 1 个点伸展到根节点, 第 r + 1 r + 1 个节点旋转到 l 1 l - 1 的下面, 此时第 r + 1 r + 1 个节点的左儿子对应的就是区间 [ l , r ] [l,r] 的节点。为了保证复杂度,像线段树那样,只在对应区间的根节点做一个懒惰标记即可。

为了方便处理区间 [ 1 , n ] [1,n] ,多建两个哨兵节点,对 [ 1 , n + 2 ] [1,n + 2] 进行建树,并将操作区间 l , r l,r 都加上 1,保证 l 1 , r + 1 l - 1,r + 1 仍在范围内,输出的时候注意不要输出这两个哨兵节点即可。

原序列 第 x 个点,就是BST上排名为 x 的点。每次查找一下排名为 x 的节点即可。

有懒惰标记自然需要有 pushdown 操作,传递标记,而在 rotate 操作会改变树形(只有 rotate 操作会改变树形,splay 操作依赖于rotate操作 ),因此在 rotate 时需要维护 懒惰标记:很简单,设当前要旋转的节点是 x,其父节点为 f,因为旋转之后 f 会变为 x 子节点, x 的一棵子树无法得到 f 的懒惰标记。在旋转之前先将 x 的懒惰标记 pushdown,再 pushdown f 的懒惰标记,标记就一定不会乱。

接下来在中序遍历和查询的时候 每次 pushdown 即可。

rotate 代码:

	void rotate(int x) {							//旋转操作,根据 x 在 f[x] 的哪一侧进行左旋和右旋 
	    int old = f[x], oldf = f[old];
	    pushdown(old); pushdown(x); 				//交换顺序也不会影响 
	    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
	}

查找第 x 个点(排名为 x 的点):

	int findx(int x) {								//查询第x个数的节点 
		int now = root;
		if (!now)  return 0;
		while(1) {
			pushdown(now);							//每次下推标记 
			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;
	}

心得:BST可以看作维护原序列按某个关键字排序后的新序列,如果没有插入、删除 和 翻转之类操作,这个新序列的相对位置不会改变,排名为 x 的点就是这个维护的新序列下标为 x 的点,这题要直接维护原序列因此按下标为排序关键字插入到BST即可。

完整代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
int n,m,a[maxn];
vector<int> tmp;
struct Splay_tree {
	int ch[maxn][2];		//ch[u][0] 表示 左二子,ch[u][1] 表示右儿子
	int f[maxn];			//当前节点的父节点 
	int sz[maxn]; 			//当前节点的子树的节点个数
	int val[maxn];			//当前节点的权值
	int cnt[maxn];			//当前节点所表示的值的个数
	int tag[maxn];
	int root;				//根节点
	int tot;				//总节点的个数 
	inline bool get(int x){
    	return ch[f[x]][1]==x;
	}
	void init() {
		root = tot = 0;
		tag[0] = f[0] = ch[0][0] = ch[0][1] = sz[0] = val[0] = cnt[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 pushdown(int rt) {					//翻转 rt 的两棵子树
		 if(!rt || !tag[rt]) return ;
		 if(ch[rt][0]) tag[ch[rt][0]] ^= 1;
		 if(ch[rt][1]) tag[ch[rt][1]] ^= 1;
		 swap(ch[rt][0],ch[rt][1]);
		 tag[rt] = 0;
	}
	void newnode(int rt,int v,int fa) {		//新建节点 
		f[rt] = fa;
		val[rt] = v; cnt[rt] = sz[rt] = 1;
		tag[rt] = ch[rt][0] = ch[rt][1] = 0;
	}
	void delnode(int rt) {
		f[rt] = sz[rt] = val[rt] = 0;
		tag[rt] = ch[rt][0] = ch[rt][1] = 0;
	}
	void build(int &rt,int l,int r,int fa) {
		if (l > r) return ;
		int mid = l + r >> 1;
		rt = mid; newnode(rt,rt,fa);
		build(ch[rt][0],l,mid - 1,rt);
		build(ch[rt][1],mid + 1,r,rt);
		pushup(rt);
	}
	void rotate(int x) {							//旋转操作,根据 x 在 f[x] 的哪一侧进行左旋和右旋 
	    int old = f[x], oldf = f[old];
	    pushdown(old); pushdown(x); 				//交换顺序也不会影响 
	    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 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)
			root = x;
	}
	int find(int x) {						//查找 x,若 x 不存在返回的是 x 的前驱或者后继 (不一定就是前驱或者后继,两者都可能)					 
		int now = root, father = 0;
		if(!now) return now;
		while(1) {
			if(val[now] == x) return now;
			if(ch[now][0] && val[now] > x) {
				father = now;
				now = ch[now][0];
			} else if(ch[now][1] && val[now] < x) {
				father = now;
				now = ch[now][1];
			} else {
				return now;
			}
		}
	}
	int findx(int x) {								//查询第x个数的节点 
		int now = root;
		if (!now)  return 0;
		while(1) {
			pushdown(now);							//每次下推标记 
			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;
	}
	inline int rank(int x){							//寻找x所在位置(排名)
	    int now = root,ans=0;
	    while(1){
	    	if(!now) break;
	        if(x < val[now])//往左子树搜索
	            now = ch[now][0];
	        else {
	            ans += (ch[now][0] ? sz[ch[now][0]] : 0);
	            //找到对应的键值,置顶now,返回
	            if(x==val[now]){splay(now,0); break;} 
	            ans += cnt[now];
	            now = ch[now][1];//往右子树
	        }    
	    }
	    return ans + 1;
	}
	void insert(int x) {				//把 x 插入到 splay中 
		int now = root, father = 0;
		if(root == 0) {
			newnode(++tot,x,0);
			root = tot;
			return ;
		}
		while(1) {
			if(val[now] == x) {
				cnt[now]++; sz[now]++;
				pushup(now); pushup(father);
				splay(now,0);
				break;
			}
			father = now;
			now = ch[now][val[now] < x];
			if(!now) {											//该节点不存在,则新建节点,并把这个节点旋转上去作为根节点 
				newnode(++tot,x,father);
				ch[father][x > val[father]] = tot;
				f[tot] = father;
				pushup(tot); pushup(father);
				splay(tot,0);
				break;
			}
		}
	}
	int prev(int x) {                               //查找值 x 的前驱节点 
        int k = find(x);
        splay(k,0);
        if(val[k] >= x) {
            k = ch[k][0];
            while(ch[k][1]) k = ch[k][1];
        }
        return k;
    }
    int nxtv(int x) {                               //查找值 x 的后继节点 
        int k = find(x);
        splay(k,0);
        if(val[k] <= x) {
            k = ch[k][1];
            while(ch[k][0]) k = ch[k][0];
        }
        return k;
    }
	void del(int x) { 								//删除权值为 x 的节点 
		if(root == 0) return ;
		int y = find(x);
		splay(y,0);
		if(val[root] != x) return ;					//不存在 
		if(cnt[root] > 1) {
			cnt[root]--; sz[root]--;
		} else if(!ch[root][0] && !ch[root][1]) {
			delnode(root); root = 0;
		} else if(!ch[root][0]) {
			int newroot = ch[root][1];
			f[ch[root][1]] = 0;
			delnode(root);
			root = newroot;
		} else if(!ch[root][1]) {
			int newroot = ch[root][0];
			f[ch[root][0]] = 0;
			delnode(root);
			root = newroot;
		} else {
			int pre = prev(x), oldroot = root;
			splay(pre,0);
			ch[root][1] = ch[oldroot][1];
			f[ch[oldroot][1]] = pre;
			delnode(oldroot);
			pushup(pre);
		}
	}
	void qry(int l,int r) {								//处理询问 
		int x = findx(l - 1), y = findx(r + 1);
		splay(x,0);
		splay(y,x);
		int p = ch[ch[root][1]][0];
		tag[p] ^= 1;
	}
	void dfs(int rt) {
		if(!rt) return ;
		pushdown(rt);
		dfs(ch[rt][0]);
		if(val[rt] > 1 && val[rt] < n + 2) tmp.push_back(val[rt]);
		dfs(ch[rt][1]);
	}
}tree;
int main() {
	scanf("%d%d",&n,&m);
	tree.init();
	tree.build(tree.root = 1,1,n + 2,0);
	while(m--) {
		int l,r;
		scanf("%d%d",&l,&r);
		l++; r++;
		tree.qry(l,r);
	}
	tree.dfs(tree.root);
	for (int i = 0; i < n; i++)
		printf("%d%s",tmp[i] - 1,i == n - 1 ? "\n" :" ");
	return 0;
}
发布了332 篇原创文章 · 获赞 10 · 访问量 1万+

猜你喜欢

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