「BZOJ3065」带插入区间第K小值 替罪羊树×线段树
题目描述
从前有\(n\)只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力\(a_i\)。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间\(k\)小值。他每次向它的随从伏特提出这样的问题: 从左往右第\(x\)个到第\(y\)个跳蚤中,a[i]第\(k\)小的值是多少。
这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王
的询问。
这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。
这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间\(k\)小值在线查询。
输入
第一行一个正整数\(n\),表示原来有\(n\)只跳蚤排成一行做早操。
第二行有\(n\)个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数\(q\),表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共\(m\)只跳蚤)
- \(Q\ x\ y\ k\): 询问从左至右第\(x\)只跳蚤到从左至右第\(y\)只跳蚤中,弹跳力第\(k\)小的跳蚤的弹跳力是多少。
\((1 ≤ x ≤ y ≤ m, 1 ≤ k ≤ y - x + 1)\) - \(M\ x\ val\): 将从左至右第x只跳蚤的弹跳力改为\(val\)。
\((1 <= x <= m)\) - \(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));
}
}
}
作者:ModestStarlight
出处:http://www.cnblogs.com/ModestStarlight/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。