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

题目描述

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

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

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

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

这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间k小值在线查询。

输入

第一行一个正整数n,表示原来有n只跳蚤排成一行做早操。
第二行有n个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数q,表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共m只跳蚤)

  1. Q x y k: 询问从左至右第x只跳蚤到从左至右第y只跳蚤中,弹跳力第k小的跳蚤的弹跳力是多少。
    (1xym,1kyx+1)
  2. M x val: 将从左至右第x只跳蚤的弹跳力改为val
    (1<=x<=m)
  3. I x val: 在从左至右第x只跳蚤的前面插入一只弹跳力为val的跳蚤。即插入后从左至右第x只跳蚤是刚插入的跳蚤。
    (1<=x<=m+1)

为了体现在线操作,设lastans为上一次查询的时候程序输出的结果,如果之前没有查询过,则lastans=0QMI操作的数全都要异或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

数据范围

n35000
插入个数35000,修改个数$ ≤ 70000 ≤ 700000 ≤ ≤ 70000$。

题解

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

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

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

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

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

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

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

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

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

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 @   ModestStarlight  阅读(341)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示