【ybt金牌导航4-4-2】【luogu P2042】维护数列(fhq Treap 做法)

维护数列

题目链接:ybt金牌导航4-4-2 / luogu P2042

题目大意

给你一个序列,要你支持一些操作。
往一个地方插入一段数,删去一段连续的数,将一段连续的数改成同一个值,将连续的一段数翻转,求出连续的一段数的和,求当前数列的最长子数列。

思路

这种题看着都知道是用平衡树来做。

看着也知道要维护很多的懒标记,非常的恶心。

这里讲的是无旋 Treap,即 fhq Treap 的做法。

无旋 Treap 的做法这里就不多说的,主要是来解决几个实现的问题。

  1. 它会插入一些数组,那一个一个点插时间复杂度太大了,会直接跑到 \(nlogn\) 级别。

我们会考虑把这个数组先建一个树,然后再用合并操作,分离出插入的位置,夹在中间合并。
但问题是你要如何块苏的建一个树呢?
那我们先看单点的插入,不难想到插入方法:
我们从根节点开始,不断的与当前点比较优先级,如果不优就往下走,找到第一个优的,然后它就变成这个位置的父亲的右儿子,这个位置就变成它的左儿子。

那可以看出它只会在右儿子上跑,那我们可以开一个栈维护。
每次即不停的从小往上看,更优就一直往上跳,跳到不优的前一个。然后就连,然后把不优的都弹出,放入你现在的。
那每个点最多入栈出栈一次,时间复杂度啊就是 \(O(n)\) 的了。

  1. 翻转

有人会想到文艺平衡树,没错,大概也是同样的方法,用懒标记。

  1. 求和最大的子序列

做线段树的时候就有这种题,大概做法是维护前缀最大和后缀最大,通过这两个来维护这个求和最大。
而且你发现这个东西翻转的时候求和最大不变,但是前缀最大和后缀最大是也要交换的。
然后再加值啊,换值的时候都要重新维护。
总的来说就是多 \(up(now)\),多 \(down(now)\)

  1. 区间赋值

不难想到是用懒标记,而且用的时候还可以把翻转的懒标记去掉。
(不过赋值完都是一样的,翻转不翻转好像都无所谓了)

  1. 关于空间

由于你要维护很多东西,而且操作次数达到了 \(4\times10^6\) 你开那么多数组会 MLE。
但你你会发现它任何时刻内,数组内最多有 \(5\times 10^5\) 个数,那也就是说,有很多的数会被删去。
那你这些数删去它们就不存在了,那它们原本占用的位置你可以重新利用。
那你可以搞一个队列记录被删去,位置还没有被重新用的数的位置,然后你要加点的时候,如果队列空了就新开空间放,否则就用队列里面的位置,然后把这个位置从队列中弹出。

然后就是痛苦的具体实现了,具体可以看看代码。

代码

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<iostream>
#define N 500051
#define INF 1e9

using namespace std;

int n, m, a[N], root, sta[N], size[N], yj[N], TOT, turn[N], change[N];
int ls[N], rs[N], val[N], lmax[N], rmax[N], sum[N], lrmax[N];
int pos, tot, c;
queue <int> rubbish;
char op, opp, oppp;

//为了节省空间,我们要将删去的点的空间用来存新的点
void clean_rubbish(int now) {//初始化数值
	size[now] = yj[now] = turn[now] = ls[now] = rs[now] = 0;
	val[now] = lmax[now] = rmax[now] = sum[now] = lrmax[now] = 0;
	change[now] = INF;
}

int find_rubbish() {
	if (!rubbish.empty()) {//用队列存因为删去而可以放的位置
		int re = rubbish.front();
		rubbish.pop();
		clean_rubbish(re);
		return re;
	}
	return ++TOT;//否则新开位置
}

int new_point(int num) {//开一个新的点
	int re = find_rubbish();
	ls[re] = rs[re] = 0;
	val[re] = sum[re] = lrmax[re] = num;
	lmax[re] = rmax[re] = max(0, num);
	yj[re] = rand();
	change[re] = INF;
	size[re] = 1;
	return re;
}

void up(int now) {//信息上传
	size[now] = size[ls[now]] + size[rs[now]] + 1;
	sum[now] = sum[ls[now]] + sum[rs[now]] + val[now];
	lmax[now] = max(lmax[ls[now]], max(0, sum[ls[now]] + val[now] + lmax[rs[now]]));
	rmax[now] = max(rmax[rs[now]], max(0, sum[rs[now]] + val[now] + rmax[ls[now]]));
	lrmax[now] = rmax[ls[now]] + val[now] + lmax[rs[now]];//这里子串不能是空的,所以不能和 0 取 max
	if (ls[now]) lrmax[now] = max(lrmax[now], lrmax[ls[now]]);
	if (rs[now]) lrmax[now] = max(lrmax[now], lrmax[rs[now]]);
}

//下放懒标记
void down_turn(int now) {//序列翻转的懒标记
	swap(ls[now], rs[now]);
	swap(lmax[now], rmax[now]);//记得翻转的话前缀最大和后缀最大就要交换
	turn[now] ^= 1;
}

void down_change(int now, int num) {//序列改值的懒标记
	val[now] = num;
	change[now] = num;
	sum[now] = size[now] * num;
	lmax[now] = rmax[now] = max(0, sum[now]);
	lrmax[now] = max(val[now], sum[now]);//还是老规矩,不是是空的
}

void down(int now) {
	if (turn[now]) {
		if (ls[now]) down_turn(ls[now]);
		if (rs[now]) down_turn(rs[now]);
		turn[now] ^= 1;
	}
	if (change[now] != INF) {
		if (ls[now]) down_change(ls[now], change[now]);
		if (rs[now]) down_change(rs[now], change[now]);
		change[now] = INF;
	}
}

int build(int num) {
	sta[0] = 0;
	int now = new_point(a[1]);
	sta[++sta[0]] = now;
	
	for (int i = 2; i <= num; i++) {
		int lst = 0;
		now = new_point(a[i]);
		
		while (sta[0] && yj[now] < yj[sta[sta[0]]]) {//用一个栈记录最右的链,维护这个链满足堆性质
			up(sta[sta[0]]);//记得要上传标记
			lst = sta[sta[0]];//这些比它大的到时都要放到它的左边
			sta[sta[0]--] = 0;
		}
		ls[now] = lst;//放到左边
		up(now);
		if (sta[0]) {//它跟上面的连
			rs[sta[sta[0]]] = now;
			up(sta[sta[0]]);
		}
		sta[++sta[0]] = now;//它就放进了这个最右的链中
	}
	
	int lst = sta[sta[0]];
	while (sta[0]) {
		up(sta[sta[0]]);
		lst = sta[sta[0]];
		sta[sta[0]--] = 0;
	}
	return lst;
}

pair <int, int> split_rnk(int now, int rnk) {//将这个树中前 rnk 小的分离出来
	if (!now) return make_pair(0, 0);
	if (!rnk) return make_pair(0, now);
	
	down(now);
	pair <int, int> re;
	if (size[ls[now]] >= rnk) {//它的右子树都会放在右边
		re = split_rnk(ls[now], rnk);//继续分割左边的树
		ls[now] = re.second;//左边的树右边的部分就接到了右边树的左儿子处
		up(now);
		re.second = now;
	}
	else {//它的左子树都会放在在左边
		re = split_rnk(rs[now], rnk - size[ls[now]] - 1);//继续分割右边的数(记得排名要减去左子树大小和本身点大小)
		rs[now] = re.first;//右边的树左边的部分就街道了左边树的右儿子处
		up(now);
		re.first = now;
	}
	
	return re;
}

int merge(int x, int y) {//合并两个 Treap(不用想 Splay 那样要一个大于另一个)
	if (x) down(x);
	if (y) down(y);
	if (!x) return y;
	if (!y) return x;
	
	if (yj[x] < yj[y]) {//左边的优先级大,它应放到上面
		rs[x] = merge(rs[x], y);//左边的右儿子跟右边合并
		up(x);
		return x;
	}
	else {//右边的优先级大,它应放到上面
		ls[y] = merge(x, ls[y]);//左边跟右边的左儿子合并
		up(y);
		return y;
	}
}

void insert(int pos, int tot) {//插入数组,就这些数组构成一个树,然后把原来的树按着要求的位置分离成两个,然后三个按顺序合并(新的放在中间)
	int nroot = build(tot);
	pair <int, int> x = split_rnk(root, pos);
	root = merge(merge(x.first, nroot), x.second);
}

void become_rubbish(int now) {//把点删去的时候记录下那些位置是被删去可以重新用的
	if (!now) return ;
	if (ls[now]) become_rubbish(ls[now]);
	rubbish.push(now);
	if (rs[now]) become_rubbish(rs[now]);
}

void delete_(int pos, int tot) {//类似插入的操作,把树分离乘三个部分(就干题目给的),中间是要删的,就记录下被删去的位置,然后把剩下左右两个合并
	pair <int, int> x = split_rnk(root, pos - 1);
	pair <int, int> y = split_rnk(x.second, tot);
	become_rubbish(y.first);
	root = merge(x.first, y.second);
}

void change_(int pos, int tot, int c) {//类似前面一样把这个区间分离出来,打上要改值的标记,然后改一下这个点的值,再重新合并回去
	pair <int, int> x = split_rnk(root, pos - 1);
	pair <int, int> y = split_rnk(x.second, tot);
	
	val[y.first] = c;
	change[y.first] = c;
	sum[y.first] = size[y.first] * c;
	lmax[y.first] = rmax[y.first] = max(0, sum[y.first]);
	lrmax[y.first] = max(val[y.first], sum[y.first]);
	turn[y.first] = 0;
	
	root = merge(x.first, merge(y.first, y.second));
} 

void turn_(int pos, int tot) {//跟上面类似,都是分离,打标记,翻转,合并
	pair <int, int> x = split_rnk(root, pos - 1);
	pair <int, int> y = split_rnk(x.second, tot);
	turn[y.first] ^= 1;
	swap(lmax[y.first], rmax[y.first]);
	swap(ls[y.first], rs[y.first]);
	
	root = merge(x.first, merge(y.first, y.second));
}

int get_sum(int pos, int tot) {//分离,直接用我们维护的 sum 数组,合并
	pair <int, int> x = split_rnk(root, pos - 1);
	pair <int, int> y = split_rnk(x.second, tot);
	
	int re = sum[y.first];
	root = merge(x.first, merge(y.first, y.second));
	return re;
}

int main() {
	srand(19491001);
	
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= n; i++) {
		scanf("%d", &a[i]);
	}
	
	root = build(n);
	
	while (m--) {
		op = getchar();
		while (op != 'I' && op != 'D' && op != 'M' && op != 'R' && op != 'G') op = getchar();
		opp = getchar();
		oppp = getchar();
		
		if (op == 'I') {
			for (int i = 1; i <= 3; i++) getchar();
			scanf("%d %d", &pos, &tot);
			for (int i = 1; i <= tot; i++) scanf("%d", &a[i]);
			
			insert(pos, tot);
			
			continue;
		}
		if (op == 'D') {
			for (int i = 1; i <= 3; i++) getchar();
			scanf("%d %d", &pos, &tot);
			
			delete_(pos, tot);
			
			continue;
		}
		if (op == 'M' && oppp == 'K') {
			for (int i = 1; i <= 6; i++) getchar();
			scanf("%d %d %d", &pos, &tot, &c);
			
			change_(pos, tot, c);
			
			continue;
		}
		if (op == 'R') {
			for (int i = 1; i <= 4; i++) getchar();
			scanf("%d %d", &pos, &tot);
			
			turn_(pos, tot);
			
			continue;
		}
		if (op == 'G') {
			for (int i = 1; i <= 4; i++) getchar();
			scanf("%d %d", &pos, &tot);
			
			printf("%d\n", get_sum(pos, tot));
			
			continue;
		}
		if (op == 'M' && oppp == 'X') {
			for (int i = 1; i <= 4; i++) getchar();
			
			printf("%d\n", lrmax[root]);
			
			continue;
		}
	}
	
	return 0;
}
posted @ 2021-05-24 16:24  あおいSakura  阅读(57)  评论(0编辑  收藏  举报