【BZOJ】3065: 带插入区间K小值

时间:2023-03-08 22:46:34
【BZOJ】3065: 带插入区间K小值

http://www.lydsy.com/JudgeOnline/problem.php?id=3065

题意:带插入、修改的区间k小值在线查询。(原序列n<=35000, 询问<=175000)

#include <bits/stdc++.h>
using namespace std; const int nTr=1000005, nSg=15000005, alphaA=4, alphaB=5; int STop;
struct Seg *Snull;
struct Seg { Seg *l, *r; int s, cnt; }Sg[nSg], *iSg=Sg, *bin[nSg];
Seg *newS() {
Seg *x;
if(!STop) x=iSg++;
else x=bin[STop--];
x->l=x->r=Snull; x->s=0; x->cnt=1;
return x;
}
void clean(Seg *&x) {
if(x!=Snull && (--x->cnt==0)) {
bin[++STop]=x;
clean(x->l); clean(x->r);
}
x=Snull;
}
Seg *merge(Seg *l, Seg *r) {
if(l==Snull) { ++r->cnt; return r; }
if(r==Snull) { ++l->cnt; return l; }
Seg *p=newS();
p->l=merge(l->l, r->l);
p->r=merge(l->r, r->r);
p->s=l->s+r->s;
return p;
}
Seg *ins(Seg *p, int k, int _s, int l, int r) {
if(p->s+_s==0) { ++Snull->cnt; return Snull; }
int mid=(l+r)>>1;
Seg *x=newS();
x->l=p->l; ++x->l->cnt;
x->r=p->r; ++x->r->cnt;
x->s=p->s+_s;
if(l==r) return x;
if(k<=mid) { --x->l->cnt; x->l=ins(p->l, k, _s, l, mid); }
else { --x->r->cnt; x->r=ins(p->r, k, _s, mid+1, r); }
return x;
}
Seg *ins(Seg *p, int k, int s) {
Seg *x=ins(p, k, s, 0, 70000);
clean(p);
return x;
}
struct node *null;
struct node {
node *c[2];
int s, k;
Seg *t;
void pushup() { s=c[0]->s+c[1]->s+1; t=ins(merge(c[0]->t, c[1]->t), k, 1); }
}Tr[nTr], *iTr=Tr, *root;
node* newT(int k) {
node *x=iTr++;
x->s=1; x->k=k; x->c[0]=x->c[1]=null; x->t=Snull; ++Snull->cnt;
return x;
}
node* flatten(node *x, node *y) {
if(x==null) return y;
clean(x->t);
x->c[1]=flatten(x->c[1], y);
return flatten(x->c[0], x);
}
node* build(node *x, int n) {
if(n==0) { x->c[0]=null; return x; }
node *y=build(x, n>>1);
node *z=build(y->c[1], n-(n>>1)-1);
y->c[1]=z->c[0];
z->c[0]=y;
y->pushup();
return z;
}
void rebuild(node *&x) {
static node y;
node *head=flatten(x, &y);
build(head, x->s);
x=y.c[0];
}
node *rB;
void insert(node *&x, int pos, int val) {
if(x==null) { x=newT(val); x->t=ins(Snull, val, 1); return; }
++x->s;
x->t=ins(x->t, val, 1);
int s=x->c[0]->s;
if(pos<=s) insert(x->c[0], pos, val);
else insert(x->c[1], pos-s-1, val);
if(max(x->c[0]->s, x->c[1]->s)*alphaB<=x->s*alphaA) { if(rB!=null) rebuild(x->c[rB==x->c[1]]), rB=null; }
else rB=x;
}
int update(node *x, int pos, int val) {
x->t=ins(x->t, val, 1);
int s=x->c[0]->s+1;
if(pos==s) swap(val, x->k);
else if(pos<s) val=update(x->c[0], pos, val);
else val=update(x->c[1], pos-s, val);
x->t=ins(x->t, val, -1);
return val;
}
struct Link {
int len;
struct Ln { int l, r; Seg *t; }val[nTr<<2];
void add(Seg *x) { if(x==Snull) return; ++len; val[len].t=x; val[len].l=len-1; val[len].r=0; val[len-1].r=len; }
void del(int x) { val[val[x].l].r=val[x].r; val[val[x].r].l=val[x].l; }
void clr() { len=0; val[0].l=val[0].r=0; }
};
void query(node *x, int R, Link &a, Link &b) {
if(x==null || !R) return;
int s=x->c[0]->s+1;
if(s<=R) a.add(x->t), b.add(x->c[1]->t), query(x->c[1], R-s, a, b);
else query(x->c[0], R, a, b);
}
int last;
Link a, b;
void Query(int x, int y, int k) {
a.clr(); b.clr();
query(root, x-1, b, a);
query(root, y, a, b);
int l=0, r=70000;
while(l<r) {
x=a.val[0].r, y=b.val[0].r;
int sum=0;
while(x) sum+=a.val[x].t->l->s, x=a.val[x].r;
while(y) sum-=b.val[y].t->l->s, y=b.val[y].r;
x=a.val[0].r, y=b.val[0].r;
if(k<=sum) {
r=(l+r)>>1;
while(x) { a.val[x].t=a.val[x].t->l; if(a.val[x].t==Snull) a.del(x); x=a.val[x].r; }
while(y) { b.val[y].t=b.val[y].t->l; if(b.val[y].t==Snull) b.del(y); y=b.val[y].r; }
}
else {
l=((l+r)>>1)+1;
k-=sum;
while(x) { a.val[x].t=a.val[x].t->r; if(a.val[x].t==Snull) a.del(x); x=a.val[x].r; }
while(y) { b.val[y].t=b.val[y].t->r; if(b.val[y].t==Snull) b.del(y); y=b.val[y].r; }
}
}
printf("%d\n", last=l);
}
void Update(int x, int val) {
update(root, x, val);
}
void Insert(int x, int val) {
rB=null;
insert(root, x, val);
if(rB!=null) rebuild(root);
}
void build(node *&x, int l, int r) {
if(l>r) return;
int mid=(l+r)>>1;
x=newT(-1);
build(x->c[0], l, mid-1);
scanf("%d", &x->k);
build(x->c[1], mid+1, r);
x->pushup();
}
void init() {
Snull=new Seg; Snull->l=Snull->r=Snull; Snull->s=0;
null=new node; null->s=null->k=0; null->t=Snull; null->c[0]=null->c[1]=null;
root=null;
}
int main() {
init();
int n, x, y, k; scanf("%d", &n);
build(root, 1, n);
scanf("%d", &n);
char s[5];
while(n--) {
scanf("%s", s); scanf("%d%d", &x, &y); x^=last, y^=last;
if(s[0]=='Q') scanf("%d", &k), k^=last, Query(x, y, k);
else if(s[0]=='M') Update(x, y);
else Insert(x-1, y);
}
return 0;
}

  

吐槽:妈呀,这题我写了12个小时.................................................................................................................................................................还是期末考试后10天内A的第一道题...我是有多颓废....

afo的节奏啊.............................................................

题解:

本题我自己yy出一种做法,线段树套splay...然后发现vfk的blog已经有这种做法...而且还说明会tle.............................所以果断没写了.....大概就是线段树维护权值,splay维护区间,插入的时候直接从线段树根走到叶子均插入区间到splay中,然后将原序列后边的值在splay里打个tag表示向后移一步...(或者还有更简单的?),修改就是先删除后插入,查询在线段树二分权值即可....

然后vfk写了好多份题解QAQ...我只是为了学替罪羊好去a掉紫荆花之恋这题..没想到颓废这一题就颓了12h...........................

原因在哪..............RERERERERERERERERERERERE............函数式线段树写跪了..............然后大概调了10小时........(第一次写引用计数啊...而且出了好多奇葩问题...)跪烂那些时间排名靠前的做法orzzzzzzzzzzzz

替罪羊树:详细看vfk论文...我就说说大概...............

插入:如果子树的size>当前根的size*alpha,那么就暴力重建 = =...可以证明复杂度均摊O(lgn)....(在论文里还有个深度平衡...其实没必要= =...学习hzwer神犇的姿势...我们只需要找到第一个大小平衡的根然后找到对应子女是大小不平衡的进行重建即可...

删除:bst一样........(好久没写过的样子..........似乎也是当子树那啥然后暴力重建....

无旋转....

所有操作复杂度都是O(lgn)的..

这里有一种很神的重建技巧...使其空间保持在O(lgn)内..(可能常数大了不少 = =)

这个操作叫拍扁(排便)23333333333333333333333

先将子树转化为链表...(炒鸡简单...定义flatten(x,y)返回x即其子树最前边的那个,且最后边后边加入一个y,则

if(x==null) return y; right(x)=flatten(right(x), y); return flatten(left(x), x);

是不是炒鸡容易理解...(递归定义啊亲..

然后是重建...(也是炒鸡简单,只不过绕了一下..定义build(x,n)表示返回当前子树的假根(是x开头长度为n的链表最后一个元素的后一个元素,不是要求的根),子树x都在这个假根的左子女中,且左子女大小为n

那么

if(n==0) { left(x)=null; return x; }
node *y=build(x, n/2);
node *z=build(right(y), n-n/2-1);
right(y)=left(z): //后边序列为右子树,其实真正得到的根是y而不是z,而由定义知道z是后边序列的假根,所以假根的左子女包含了所有的原列表的元素
left(z)=y; //定义得出
return z;

然后这就是拍扁....

最后是插入的一个技巧,请看代码....

然后进入正题..(妈呀,果然垃圾回收是正题吗...

首先膜拜hzwer的垃圾回收,因为我没看懂QAQ,为什么在函数式插入那里竟然直接修改了!!!(@hzwer

然后膜拜vfk的垃圾回收,恩.我选择的就是这种..............引用计数.................

引用计数..由名字可以看出是由引用决定了当前元素是否存在的.....

那么我们维护一个cnt值,表示当前元素被多少所有存在的元素引用(注意一定是存在的元素,即不是引用,例如x是元素,node *y=x,y只能算一种引用而不是元素,其实可以直接理解为,每个元素的l和r指向的元素就为调用1次)

真正的元素一定是先new了一个节点,然后更改的那个。

因此我们发现每一次new的时候一定是被别的元素给引用过了...所以cnt=1

每一次插入的时候,如果最终的size=0,说明这个元素已经无用...给null的cnt+1,返回null(即表示用ins赋值的那个元素引用了null

然后其实我们ins递归定义是返回一个被引用的指针,即cnt是要被++的

随便搞搞就行辣...

接下来是两棵函数式线段树合并...递归定义merge(a,b)返回一棵合并了a和b的新元素(或者是其中之一的引用)

首先如果有一棵是null,那么直接返回另一棵的元素,即引用,所以cnt要++

而如果都不是null,由于我们是函数式...从不修改任何东西..于是我们就new一个元素....然后大小为两棵线段树的大小之和,然后递归定义我们左右子女的引用....

然后就好辣...

最后是垃圾回收....如果cnt为0的时候就要去掉....

唔?你问我什么时候调用回收?当然是一个新的元素的引用少了一个时调用啦...比如说t=ins(t, a, b); 其实t原来指向的元素少了t这个东西引用,,,因此要这样p=t; t=ins(p, a, b); clean(p);因为t没有引用原来引用的东西辣....

然后写完后喜闻乐见的时间垫底了...............