ZOJ2112 动态区间Kth(单点修改) 线段树+Treap写法

---恢复内容开始---

题意:给出一个序列和操作次数, 每次操作修改一个位置的数 或者 询问一个区间第k小的数

分析:单点修改可以考虑线段树, 区间排名可以用平衡树 所以线段树+Treap

用一颗线段树将序列划分 每颗Treap中插入的是对应区间的数

在每个数加入时, 顺便将该数插入线段树中包含该位置的那些区间上的Treap即可

单点修改同理, 将所有包含要修改的位置的区间上的Treap都删去原数并插入新数

询问第k小的数:由于询问的区间不一定恰好对应某棵Treap, 不便直接求出名次,

但是能够直接求出询问区间中不大于某个数的数的个数, 则对于x, 若满足不大于x

的数有多于k个, 且不大于x-1的数少于k个, 就是原序列中第k小的数二分该数即可

#include<algorithm>
#include<cstring>
#include<cstdio>

const int maxn = 1000007;
int n, m, ret, arr[maxn];
char opt[3];
//...
int read() {
	int x = 0, f = 1; char ch = getchar();
	while(ch < ‘0‘ || ch > ‘9‘) {if(ch == ‘-‘) f = -1; ch = getchar();}
	while(ch >= ‘0‘ && ch <= ‘9‘) {x = x * 10 + ch - ‘0‘; ch = getchar();}
	return x * f;
}
struct Treap {
	int root[maxn], size, lc[maxn], rc[maxn], val[maxn], sz[maxn], cnt[maxn], rnd[maxn];
	void init() {
		memset(root, 0, sizeof(root));
		size = 0;
	}
	void update(int k) {
		sz[k] = sz[lc[k]] + sz[rc[k]] + cnt[k];
	}
	void l_rot(int &k) {
		int t = rc[k]; rc[k] = lc[t]; lc[t] = k;
		sz[t] = sz[k]; update(k); k = t;
	}
	void r_rot(int &k) {
		int t = lc[k]; lc[k] = rc[t]; rc[t] = k;
		sz[t] = sz[k]; update(k); k = t;
	}
	void insert(int &k, int x) {
		if(k == 0) {
			k = ++size;
			lc[k] = rc[k] = 0;//这里写了初始化时就不用全部清零, 节省时间
			sz[k] = cnt[k] = 1;
			val[k] = x; rnd[k] = rand();
			return;
		}
		sz[k]++;
		if(x == val[k]) {cnt[k]++; return;}
		if(x > val[k]) {
			insert(rc[k], x);
			if(rnd[rc[k]] < rnd[k]) l_rot(k);
		} else {
			insert(lc[k], x);
			if(rnd[lc[k]] < rnd[k]) r_rot(k);
		}
	}
	void del(int &k, int x) {
		if(k == 0) return;
		if(x == val[k]) {
			if(cnt[k] > 1) {cnt[k]--; sz[k]--; return;}
			if(lc[k] * rc[k] == 0) {k = lc[k] + rc[k]; return;}//考虑某个儿子为空则直接删除
			if(rnd[lc[k]] < rnd[rc[k]]) r_rot(k), del(k, x);
			else l_rot(k), del(k, x);
		} else if(x < val[k]) sz[k]--, del(lc[k], x);
		else sz[k]--, del(rc[k], x);
	}
	void RaNk(int k, int x) {//数x在以k为根的子树中小于等于x的数的数量(不同于求rank)
		if(k == 0) return;
		if(x >= val[k]) {
			ret += sz[lc[k]] + cnt[k];//!!!
			RaNk(rc[k], x);
		} else RaNk(lc[k], x);
	}
}Tr;

void Insert(int k, int l, int r, int x, int num) {
	Tr.insert(Tr.root[k], num);
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(x <= mid) Insert(k << 1, l, mid, x, num);
	else Insert(k << 1 | 1, mid + 1, r, x, num);
}
void Modify(int k, int l, int r, int x, int to) {
	Tr.del(Tr.root[k], arr[x]);
	Tr.insert(Tr.root[k], to);
	if(l == r) return;
	int mid = (l + r) >> 1;
	if(x <= mid) Modify(k << 1, l, mid, x, to);
	else Modify(k << 1 | 1, mid + 1, r, x, to);
}
void Query(int k, int l, int r, int s, int t, int num) {
	if(l == s && t == r) {Tr.RaNk(Tr.root[k], num); return;}
	int mid = (l + r) >> 1;
	if(mid >= t) Query(k << 1, l, mid, s, t, num);
	else if(mid < s) Query(k << 1 | 1, mid + 1, r, s, t, num);
	else {
		Query(k << 1, l, mid, s, mid, num);
		Query(k << 1 | 1, mid + 1, r, mid + 1, t, num);
	}
}
int main() {
	int T, x, y, z; T = read();
	while(T--) {
		Tr.init(); n = read(); m = read();
		for(int i = 1; i <= n; i++) {
			arr[i] = read();
			Insert(1, 1, n, i, arr[i]);
		}
		for(int i = 1; i <= m; i++) {
			scanf("%s", opt);
			if(opt[0] == ‘C‘) {
				x = read(); y = read();
				Modify(1, 1, n, x, y);
				arr[x] = y;//更新每次修改该位置时需在Treap中删去的数
			} else {
				x = read(); y = read(); z = read();
				int l = 0, r = 1e9;
				while(l <= r) {
					int mid = (l + r) >> 1;
					ret = 0; Query(1, 1, n, x, y, mid);
					if(ret >= z) r = mid - 1;
					else l = mid + 1;
				}
				printf("%d\n", l);
			}
		}
	}
	return 0;
}

---恢复内容结束---

时间: 2024-08-02 11:04:26

ZOJ2112 动态区间Kth(单点修改) 线段树+Treap写法的相关文章

【线段树】线段树系列 0.1单点修改单点求和线段树

终于搞定了单点修改线段树...3个月..操蛋..根本没有模板题..觉得太弱了...艹蛋...必须进一步更新我的版本啊 #include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> using namespace std; struct node { int left,right,value; }; node point[100]; int father[100]; int g; v

Hdu1166单点更新线段树

入门线段树,单点更新.写了几遍,都是学着notonlysuccess写的. #include <cstdio> #include <cstring> #include <algorithm> #include <climits> #include <string> #include <iostream> #include <map> #include <cstdlib> #include <list>

区间求最值 线段树

湖南师范大学 11460 区间求最值 区间求最值   Problem description   给定一个长度为N 的数组,有q个询问,每个询问是求在数组的一段区间内那个元素的因子的个数最大,比如24的因子的个数就是8.  Input   首先是一个整数t,表示有t组测试数据,每组测试数据的第一行是一个整数N(1<=N<=10^6),第二行有N个整数ai(1<=ai<=10^6,i=1,2,.....N)表示数组的元素.第三行有一个整数q(1<=q<=10^5),代表有

BZOJ_1901_&amp;_ZJU_2112_Dynamic_Rankings(主席树+树状数组/线段树+(Treap/Splay))

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=1901 给出一个长度为n的数列A,有m次询问,询问分两种:1.修改某一位置的值;2.求区间[l,r]内的第k小的值. 分析 对于只有第一种询问的问题: POJ_2104_Kth(主席树) 现在要求动态.我们思考这样一个问题:把求区间第k小的问题变成求区间和值的问题,这个好解决吧?对于静态的问题,我们使用前缀和即可解决,那么对于动态的呢?使用树状数组维护前缀和.那么现在把问题变回求区间第k小值的

BZOJ 3196 二逼平衡树 线段树+treap

题意:链接 方法:线段树+treap的模板题 题解: 首先是对于整个树的定义,其实treap部分并没有什么区别,只不过是单root改变为多root而已. #include <math.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <algorithm> #define lson l,mid,rt<<1 #define rson mid+1,

hihoCoder #1078 : 线段树的区间修改(线段树区间更新板子题)

#1078 : 线段树的区间修改 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 对于小Ho表现出的对线段树的理解,小Hi表示挺满意的,但是满意就够了么?于是小Hi将问题改了改,又出给了小Ho: 假设货架上从左到右摆放了N种商品,并且依次标号为1到N,其中标号为i的商品的价格为Pi.小Hi的每次操作分为两种可能,第一种是修改价格——小Hi给出一段区间[L, R]和一个新的价格NewP,所有标号在这段区间中的商品的价格都变成NewP.第二种操作是询问——小Hi给出一段

【BZOJ3110】【整体二分+树状数组区间修改/线段树】K大数查询

Description 有N个位置,M个操作.操作有两种,每次操作如果是1 a b c的形式表示在第a个位置到第b个位置,每个位置加入一个数c 如果是2 a b c形式,表示询问从第a个位置到第b个位置,第C大的数是多少. Input 第一行N,M 接下来M行,每行形如1 a b c或2 a b c Output 输出每个询问的结果 Sample Input 2 5 1 1 2 1 1 1 2 2 2 1 1 2 2 1 1 1 2 1 2 3 Sample Output 1 2 1 HINT

WHYZOJ-#53 线段树区间修改(线段树)

[题目描述]: 如题,已知一个数列,你需要进行下面两种操作: 1.将某区间每一个数加上x 2.求出某区间每一个数的和 [输入描述]: 第一行包含两个整数N.M,分别表示该数列数字的个数和操作的总个数. 第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值. 接下来M行每行包含3或4个整数,表示一个操作,具体如下: 操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k 操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和 [输出描述]: 输出包含若干行整

【USACO 2008 Nov Gold】 3.Light Switching(lites 开关灯) 区间修改线段树

题意: n.m,n个灯,m次操作 两种操作 0: 这段区间全部状态取反,初始全部为0 1: 询问这段区间有几个灯是亮的. 裸线段树,弱爆了. #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #define N 101000 #define inf 0x3f3f3f3f using namespace std; struct Segment_Tree {