[BZOJ3065]带插入区间K小值

Posted xjr_01

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[BZOJ3065]带插入区间K小值相关的知识,希望对你有一定的参考价值。

[BZOJ3065]带插入区间K小值

试题描述

从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i]。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间k小值。他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少。
这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问。
这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。(orz 主席树)
这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间k小值在线查询。

输入

第一行一个正整数n,表示原来有n只跳蚤排成一行做早操。
第二行有n个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数q,表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共m只跳蚤)
1. Q x y k: 询问从左至右第x只跳蚤到从左至右第y只跳蚤中,弹跳力第k小的跳蚤的弹跳力是多少。
    (1 <= x <= y <= m, 1 <= k <= y - x + 1)
2. M x val: 将从左至右第x只跳蚤的弹跳力改为val。
    (1 <= x <= m)
3. I x val: 在从左至右第x只跳蚤的前面插入一只弹跳力为val的跳蚤。即插入后从左至右第x只跳蚤是我刚插入的跳蚤。
    (1 <= x <= m + 1)

为了体现在线操作,设lastAns为上一次查询的时候程序输出的结果,如果之前没有查询过,则lastAns = 0。
则输入的时候实际是:
Q _x _y _k ------> 表示 Q _x^lastAns _y^lastAns _k^lastAns
M _x _val  ------> 表示 M _x^lastAns _val^lastAns
I _x _val  ------> 表示 I _x^lastAns _val^lastAns
简单来说就是操作中输入的整数都要异或上一次询问的结果进行解码。

(祝Pascal的同学早日转C++,就不提供pascal版的描述了。)

输出

对于每个询问输出回答,每行一个回答。

输出示例

10
10 5 8 28 0 19 2 31 1 22
30
I 6 9
M 1 11
I 8 17
M 1 31
M 6 26
Q 2 7 6
I 23 30
M 31 7
I 22 27
M 26 18
Q 26 17 31
I 5 2
I 18 13
Q 3 3 3
I 27 19
Q 23 23 30
Q 5 13 5
I 3 0
M 15 27
Q 0 28 13
Q 3 29 11
M 2 8
Q 12 5 7
I 30 19
M 11 19
Q 17 8 29
M 29 4
Q 3 0 12
I 7 18
M 29 27

输出示例

28
2
31
0
14
15
14
27
15
14

数据规模及约定

此题作为一个小小的研究来搞吧~做法有很多~不知道这题究竟有多少种做法。
请自觉O(log^2n),我故意卡块状链表,块链A了的请受我深情一拜……
A掉的同学请在Discuss里面简要说下自己的做法吧~
原序列长度 <= 35000
插入个数 <= 35000,修改个数 <= 70000,查询个数 <= 70000  ,0 <= 每时每刻的权值 <= 70000
由于是OJ上的题,所以数据无梯度。为了防止卡OJ,本题只有4组数据

题解

思路很简单,树套树,外层是维护位置的替罪羊树,内层是维护权值的线段树。

一定要时时记得加取址符号,不然你怎么改变你父亲(节点)都不会理你;把节点扔进垃圾桶时记得把他亲戚(父亲、左右儿子等)清空,虽然扔进垃圾桶我们还是要保证它干干净净的对吧;权值线段树若是该节点 sumv 变成 0 了就都扔进垃圾桶,别让它站在那里浪费青春内存。

哦对了,这题得卡卡常数。因为是树套树,重建一次的代价多了一个甚至几个 log,所以替罪羊树的那个重建的比例最好取大一点,我这次取 0.6 过不了,取 0.666 卡线过(59190 ms / 60 sec),取 0.75 也卡线过(41456 ms / 60 sec)。

最后,相信自己一定能调出来。

P.S. 样例很良心的,过了样例正确性(注意仅仅是正确性)就没什么问题了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <vector>
#include <queue>
#include <cstring>
#include <string>
#include <map>
#include <set>
using namespace std;

int read() {
	int x = 0, f = 1; char c = getchar();
	while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
	while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
	return x * f;
}

#define maxval 70000
#define maxn 70010
#define maxnode 10000010

// segment tree
int ToTs, sumv[maxnode], lc[maxnode], rc[maxnode], recs[maxnode], rcnts;
int getsnode() {
	if(rcnts) {
		int o = recs[rcnts--];
		sumv[o] = lc[o] = rc[o] = 0;
		return o;
	}
	return ++ToTs;
}
void update(int& o, int L, int R, int x, int v) {
	if(!o) o = getsnode();
	if(L == R) {
		sumv[o] += v;
		if(!sumv[o]) recs[++rcnts] = o, o = 0;
		return ;
	}
	int M = L + R >> 1;
	if(x <= M) update(lc[o], L, M, x, v);
	else update(rc[o], M+1, R, x, v);
	sumv[o] = (lc[o] ? sumv[lc[o]] : 0) + (rc[o] ? sumv[rc[o]] : 0);
	if(!sumv[o]) recs[++rcnts] = o, o = 0;
	return ;
}
void recycles(int& o) {
	if(!o) return ;
	recycles(lc[o]); recycles(rc[o]);
	lc[o] = rc[o] = sumv[o] = 0;
	recs[++rcnts] = o; o = 0;
	return ;
}

// scapegoat tree
struct Node {
	int rt, siz, v;
	Node() {}
	Node(int _, int __): rt(_), v(__) {}
} ns[maxn];
int rt, ToT, fa[maxn], ch[maxn][2];
int getnode() { return ++ToT; }
void maintain(int o) {
	ns[o].siz = 1;
	for(int i = 0; i < 2; i++) if(ch[o][i])
		ns[o].siz += ns[ch[o][i]].siz;
	return ;
}
const double Bili = .75;
bool unbal(int o) {
	return max(ch[o][0] ? ns[ch[o][0]].siz : 0, ch[o][1] ? ns[ch[o][1]].siz : 0) > Bili * ns[o].siz;
}
int rb;
void insert(int& o, int k, int v) {
	if(!o) {
		ns[o = getnode()] = Node(getsnode(), v);
//		printf("add_o: %d\n", o);
		update(ns[o].rt, 0, maxval, v, 1);
		return maintain(o);
	}
	update(ns[o].rt, 0, maxval, v, 1);
	int ls = ch[o][0] ? ns[ch[o][0]].siz : 0;
	if(k <= ls + 1) insert(ch[o][0], k, v), fa[ch[o][0]] = o;
	else insert(ch[o][1], k - ls - 1, v), fa[ch[o][1]] = o;
	maintain(o);
	if(unbal(o)) rb = o;
//	printf("insert_o: %d %d %d\n", o, ns[o].siz, sumv[ns[o].rt]);
	return ;
}
int cntn, get[maxn];
void Getnode(int o) {
	if(!o) return ;
	Getnode(ch[o][0]);
	get[++cntn] = o;
	Getnode(ch[o][1]);
	fa[o] = ch[o][0] = ch[o][1] = 0;
	recycles(ns[o].rt);
	return ;
}
void build(int& o, int l, int r) {
	if(l > r) return ;
	int mid = l + r >> 1; o = get[mid];
	for(int i = l; i <= r; i++) update(ns[o].rt, 0, maxval, ns[get[i]].v, 1);
	build(ch[o][0], l, mid - 1); build(ch[o][1], mid + 1, r);
	if(ch[o][0]) fa[ch[o][0]] = o;
	if(ch[o][1]) fa[ch[o][1]] = o;
	return maintain(o);
}
void rebuild(int& o) {
	cntn = 0; Getnode(o);
	build(o, 1, cntn);
	return ;
}
void Insert(int k, int v) {
	rb = 0; insert(rt, k, v);
	if(!rb) return ;
	int frb = fa[rb];
	if(!frb) rebuild(rt), fa[rt] = 0;
	else if(ch[frb][0] == rb) rebuild(ch[frb][0]), fa[ch[frb][0]] = frb;
	else rebuild(ch[frb][1]), fa[ch[frb][1]] = frb; // */
	return ;
}
int Modify(int o, int k, int v) {
	if(!o) return -233;
	update(ns[o].rt, 0, maxval, v, 1);
	int ls = ch[o][0] ? ns[ch[o][0]].siz : 0;
	if(k == ls + 1) {
		int tmpv = ns[o].v;
		ns[o].v = v;
		update(ns[o].rt, 0, maxval, tmpv, -1);
		return tmpv;
	}
	int tmpv;
	if(k < ls + 1) tmpv = Modify(ch[o][0], k, v);
	else tmpv = Modify(ch[o][1], k - ls - 1, v);
	if(tmpv >= 0) update(ns[o].rt, 0, maxval, tmpv, -1);
//	printf("Modify_o: %d %d %d\n", o, ns[o].siz, sumv[ns[o].rt]);
	return tmpv;
}
int No[maxn], cn, val[maxn], cv;
void getnv(int o, int l, int ql, int qr) {
	if(!o) return ;
	int ls = ch[o][0] ? ns[ch[o][0]].siz : 0;
	int r = l + ns[o].siz - 1;
	if(ql <= l && r <= qr){ No[++cn] = ns[o].rt; return ; }
	if(ql <= l + ls - 1) getnv(ch[o][0], l, ql, qr);
	if(ql <= l + ls && l + ls <= qr) val[++cv] = ns[o].v;
	if(qr > l + ls) getnv(ch[o][1], l + ls + 1, ql, qr);
	return ;
}

int solve(int ql, int qr, int k) {
	cn = cv = 0;
	getnv(rt, 1, ql, qr);
	/*for(int i = 1; i <= cn; i++) printf("%d(%d)%c", No[i], sumv[No[i]], i < cn ? ‘ ‘ : ‘\n‘);
	for(int i = 1; i <= cv; i++) printf("%d%c", val[i], i < cv ? ‘ ‘ : ‘\n‘); // */
	int l = 0, r = maxval;
	while(l < r) {
		int mid = l + r >> 1, sum = 0;
		for(int i = 1; i <= cn; i++) if(No[i]) sum += sumv[lc[No[i]]];
		for(int i = 1; i <= cv; i++) if(l <= val[i] && val[i] <= mid) sum++;
//		printf("[%d, %d] %d %d\n", l, r, sum, k);
		if(sum < k) {
			k -= sum;
			l = mid + 1;
			for(int i = 1; i <= cn; i++) if(No[i]) No[i] = rc[No[i]];
		}
		else {
			r = mid;
			for(int i = 1; i <= cn; i++) if(No[i]) No[i] = lc[No[i]];
		}
	}
	return l;
}

int ans[maxn], cans;
int main() {
	int n = read();
	for(int i = 1; i <= n; i++) ns[getnode()] = Node(getsnode(), read()), get[i] = i;
	build(rt, 1, n);
	/*printf("%d %d\n", ToT, rt);
	for(int i = 1; i <= ToT; i++) printf("%d: %d(%d %d %d %d)\n", i, ns[i].v, ch[i][0], ch[i][1], ns[i].rt, sumv[ns[i].rt]);*/
	int q = read(), lst = 0;
	char cmd[2];
	while(q--) {
		scanf("%s", cmd);
		if(cmd[0] == ‘Q‘) {
			int l = read() ^ lst, r = read() ^ lst, k = read() ^ lst;
//			printf("Q %d %d %d\n", l, r, k);
			ans[++cans] = lst = solve(l, r, k);
//			printf("%d\n", lst); // lst = 0;
		}
		if(cmd[0] == ‘M‘) {
			int k = read() ^ lst, v = read() ^ lst;
//			printf("M %d %d\n", k, v);
			Modify(rt, k, v);
		}
		if(cmd[0] == ‘I‘) {
			int k = read() ^ lst, v = read() ^ lst;
//			printf("I %d %d\n", k, v);
			Insert(k, v);
		}
	}
	for(int i = 1; i <= cans; i++) printf("%d\n", ans[i]);
	
	return 0;
}

 

以上是关于[BZOJ3065]带插入区间K小值的主要内容,如果未能解决你的问题,请参考以下文章

[BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树

Bzoj3065 带插入区间K小值

bzoj 3065 带插入区间k小值

BZOJ3065 带插入区间K小值

[BZOJ3065]带插入区间K小值

[bzoj3065] 带插入区间第k小值 [重量平衡树套线段树]