• 【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 imes10^6) 你开那么多数组会 MLE。
    但你你会发现它任何时刻内,数组内最多有 (5 imes 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
    ", get_sum(pos, tot));
    			
    			continue;
    		}
    		if (op == 'M' && oppp == 'X') {
    			for (int i = 1; i <= 4; i++) getchar();
    			
    			printf("%d
    ", lrmax[root]);
    			
    			continue;
    		}
    	}
    	
    	return 0;
    }
    
  • 相关阅读:
    DirectX标准规定 DirectX和OpenGL的不同
    Android 抽屉效果的导航菜单实现
    Servlet基础(三) Servlet的多线程同步问题
    Java微服务之Spring Boot on Docker
    Spring Cloud 微服务架构学习笔记与示例
    从你的全世界路过—一群程序员的稻城亚丁游记
    从一个国内普通开发者的视角谈谈Sitecore
    吴军《硅谷来信》思维导图笔记
    .NET Core微服务之基于Jenkins+Docker实现持续部署(Part 1)
    2018OKR年中回顾
  • 原文地址:https://www.cnblogs.com/Sakura-TJH/p/YBT_JPDH_4-4-2.html
Copyright © 2020-2023  润新知