「BZOJ3065」带插入区间第K小值 替罪羊树×线段树

题目描述

从前有\(n\)只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力\(a_i\)。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间\(k\)小值。他每次向它的随从伏特提出这样的问题: 从左往右第\(x\)个到第\(y\)个跳蚤中,a[i]第\(k\)小的值是多少。

这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王
的询问。

这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。

这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。

这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间\(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、M、I\)操作的数全都要异或\(lastans\)才是真正输入。

输出

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

样例

样例输入

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

数据范围

\(n ≤ 35000\)
插入个数\(≤ 35000\),修改个数$ ≤ 70000\(,查询个数\) ≤ 70000$。
$0 ≤ \(任意时刻的权值\)≤ 70000$。

题解

神题给跪了……
怕不是可以当成一个模板背……

不过如果不算低估替罪羊套线段树的巨量内存消耗而造成的\(RE\),可以算是\(1A\)……还是切入正题吧,不要东扯西扯了

如果我们无视插入这个操作,就是一道傻逼的树状数组套主席树模板了。所以我们首先要解决插入的问题。

如果可以离线,我们可以把所有会插入元素的位置先空出来(可以假装存在,赋一个\(inf\),保证不会被查到,然后把插入变成修改即可),做区间第\(k\)小就没有问题了。
然而偏偏这道题要强制在线,我们就必须想办法动态维护线段树序列。鉴于可能会在任意一个位置插入,我们自然想到了平衡树维护。

那么一个比较简陋的思路就出现了,我们用一棵平衡树维护序列,负责插入,然后套一个线段树,负责修改和询问。不过这个思路还很不完善——查询的时候怎么查?发现可以类似于树状数组套主席树的方法,平衡树的每个节点不只保存这个节点的信息,而是整棵子树的信息。

对于每个查询,我们在平衡树上找到对应的节点(这些节点的平衡树信息合并后就是询问的区间,显然有些节点需要加上,有些需要减掉,所以这些点是加权的\((1\ or\ -1)\)),进行线段树上的二分答案,就可以查询出答案。

但是我们会发现,平衡树没有那么容易套其他的树,因为平衡树的结构是会改变的,一旦旋转起来\((AVL、SBT、Splay)\)或是合并和拆分\((Treap)\),线段树的形态也会发生改变,所以我们需要一种更稳定的结构——替罪羊树(都是套路)。

然后就使劲儿写吧……反正\(200+\)行是稳了……

\(PS:\)注意一下内存的问题,线段树必须写成动态开点,并且内存要记得回收,不然\(RE\)\(MLE\)稳稳的。还有,标题里的\(\times\)是套的意思,与\(+\)不一样。

\(Code:\)

#include <stack>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 200005
#define M 20000005
#define seg 0, 70000
#define lim 0.77
stack<int>pool;
int n, m;
struct SGT
{
	int lc, rc, sz;
}S[M];
struct SCT
{
	int root, cnt;
	int lc[N], rc[N];
	int rt[N], dt[N], sz[N];
	int arr[N], num;
	int A[2][N << 2], ct[2];
	//线段树部分
	int Newnode()//新建节点
	{
		int x = pool.top();
		pool.pop();
		return x;
	}
	void Recycle(int &x)//回收空间
	{
		pool.push(x);
		S[x].lc = S[x].rc = S[x].sz = 0;
		x = 0;
	}
	void Insert(int &q, int l, int r, int a, int v)//插入元素
	{
		if (!q)
			q = Newnode();
		S[q].sz += v;
		if (l == r)
			return;
		int mid = (l + r) >> 1;
		if (a <= mid)
			Insert(S[q].lc, l, mid, a, v);
		else
			Insert(S[q].rc, mid + 1, r, a, v);
	}
	void Delete(int &q)//回收树
	{
		if (!q)
			return;
		Delete(S[q].lc);
		Delete(S[q].rc);
		Recycle(q);
	}
	//替罪羊树部分
	void Pushup(int q)
	{
		sz[q] = sz[lc[q]] + sz[rc[q]] + 1;
	}
	void Getarray(int q)//拍扁
	{
		if (lc[q])
			Getarray(lc[q]);
		arr[++num] = q;
		if (rc[q])
			Getarray(rc[q]);
	}
	void Rebuild(int &q, int l, int r)//重建
	{
		if (l > r)
		{
			q = 0;
			return;
		}
		int mid = (l + r) >> 1;
		q = arr[mid];
		Delete(rt[q]);
		for (int i = l; i <= r; i++)
			Insert(rt[q], seg, dt[arr[i]], 1);
		Rebuild(lc[q], l, mid - 1);
		Rebuild(rc[q], mid + 1, r);
		Pushup(q);
	}
	void Maintain(int &q)//整个拍扁重建环节
	{
		num = 0;
		Getarray(q);
		Rebuild(q, 1, num);
	}
	void Insert(int &q, int k, int a)//插入在第k个元素前
	{
		if (!q)
		{
			q = ++cnt;
			dt[q] = a;
			sz[q] = 1;
			Insert(rt[q], seg, a, 1);
			return;
		}
		Insert(rt[q], seg, a, 1);
		if (k <= sz[lc[q]] + 1)
			Insert(lc[q], k, a);
		else
			Insert(rc[q], k - sz[lc[q]] - 1, a);
		Pushup(q);
	}
	void Check(int &q, int k)//从上往下检查重建
	{
		if (sz[q] * lim < sz[lc[q]] || sz[q] * lim < sz[rc[q]])
		{
			Maintain(q);
			return;
		}
		if (k == sz[lc[q]] + 1)
			return;
		if (k <= sz[lc[q]] + 1)
			Check(lc[q], k);
		else
			Check(rc[q], k - sz[lc[q]] - 1);
	}
	int Modify(int &q, int k, int a)//修改第k个元素
	{
		if (k == sz[lc[q]] + 1)
		{
			int b = dt[q];
			Insert(rt[q], seg, a, 1);
			Insert(rt[q], seg, b, -1);
			dt[q] = a;
			return b;
		}
		int b;
		if (k <= sz[lc[q]] + 1)
			b = Modify(lc[q], k, a);
		else
			b = Modify(rc[q], k - sz[lc[q]] - 1, a);
		Insert(rt[q], seg, a, 1);
		Insert(rt[q], seg, b, -1);
		return b;
	}
	void Getarray(int q, int k, int tp)//搞出数组
	{
		if (!q)
			return;
		if (k <= sz[lc[q]])
		{
			Getarray(lc[q], k, tp);
			return;
		}
		A[tp][++ct[tp]] = rt[q];
		if (rc[q])
			A[!tp][++ct[!tp]] = rt[rc[q]];
		Getarray(rc[q], k - sz[lc[q]] - 1, tp);
	}
	int Query(int l, int r, int k, bool tp)//二分答案
	{
		if (l == r)
			return l;
		int sum = 0;
		for (int i = 1; i <= ct[0]; i++)
			sum -= S[S[A[0][i]].lc].sz;
		for (int i = 1; i <= ct[1]; i++)
			sum += S[S[A[1][i]].lc].sz;
		int mid = (l + r) >> 1;
		if (sum < k)
		{
			for (int i = 1; i <= ct[0]; i++)
				A[0][i] = S[A[0][i]].rc;
			for (int i = 1; i <= ct[1]; i++)
				A[1][i] = S[A[1][i]].rc;
			return Query(mid + 1, r, k - sum, tp);
		}
		for (int i = 1; i <= ct[0]; i++)
			A[0][i] = S[A[0][i]].lc;
		for (int i = 1; i <= ct[1]; i++)
			A[1][i] = S[A[1][i]].lc;
		return Query(l, mid, k, tp);
	}
	int Query(int l, int r, int k)//查询[l, r]之间的第k小
	{
		ct[0] = ct[1] = 0;
		Getarray(root, l - 1, 0);
		Getarray(root, r, 1);
		return Query(seg, k, 0);
	}
}T;
int main()
{
	for (int i = 20000000; i >= 1; i--)
		pool.push(i);
	scanf("%d", &n);
	for (int i = 1; i <= n; i++)
	{
		int a;
		scanf("%d", &a);
		T.Insert(T.root, i, a);
		T.Check(T.root, i);
	}
	scanf("%d", &m);
	int lastans = 0, x, y, k;
	char opt[10];
	for (int i = 1; i <= m; i++)
	{
		scanf("%s", opt);
		if (opt[0] == 'I')
		{
			scanf("%d%d", &x, &y);
			x ^= lastans;
			y ^= lastans;
			T.Insert(T.root, x, y);
			T.Check(T.root, x);
		}
		if (opt[0] == 'M')
		{
			scanf("%d%d", &x, &y);
			x ^= lastans;
			y ^= lastans;
			T.Modify(T.root, x, y);
		}
		if (opt[0] == 'Q')
		{
			scanf("%d%d%d", &x, &y, &k);
			x ^= lastans;
			y ^= lastans;
			k ^= lastans;
			printf("%d\n", lastans = T.Query(x, y, k));
		}
	}
}
posted @ 2018-03-13 14:25  ModestStarlight  阅读(335)  评论(0编辑  收藏  举报