BZOJ 3065 带插入区间K小值 (树套树、替罪羊树套线段树)

BZOJ3065 带插入区间K小值

题意

Description
从前有 n n 只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i]。跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴。这时跳蚤国王决定理性愉悦一下,查询区间 k k 小值。他每次向它的随从伏特提出这样的问题: 从左往右第x个到第 y y 个跳蚤中,a[i] k k 小的值是多少。
这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问。
这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少。
这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问。(orz主席树)
这时伏特发现有些迟到的跳蚤会插入到这一行的某个位置上,他感到非常生气,因为……他不会做了。
请你帮一帮伏特吧。
快捷版题意:带插入、修改的区间 k k 小值在线查询。
Input
第一行一个正整数n,表示原来有 n n 只跳蚤排成一行做早操。
第二行有n个用空格隔开的非负整数,从左至右代表每只跳蚤的弹跳力。
第三行一个正整数 q q ,表示下面有多少个操作。
下面一共q行,一共三种操作对原序列的操作:(假设此时一共 m m 只跳蚤)
1. Q x y k: 询问从左至右第x只跳蚤到从左至右第y只跳蚤中,弹跳力第k小的跳蚤的弹跳力是多少。( 1<=x<=y<=m,1<=k<=yx+1 1 <= x <= y <= m , 1 <= k <= y − x + 1 )
2. M x val: 将从左至右第x只跳蚤的弹跳力改为val。( 1<=x<=m 1 <= x <= m )
3. I x val: 在从左至右第x只跳蚤的前面插入一只弹跳力为val的跳蚤。即插入后从左至右第x只跳蚤是我刚插入的跳蚤。 ( 1<=x<=m+1 1 <= x <= m + 1 )
为了体现在线操作,设 lastAns l a s t A n s 为上一次查询的时候程序输出的结果,如果之前没有查询过,则 lastAns=0 l a s t A n s = 0
则输入的时候实际是:
Q _x _y _k ——> 表示 Q Q xlastAns x ⨁ l a s t A n s ylastAns klastAns
M _x _val ——> 表示 M M xlastAns x ⨁ l a s t A n s vallastAns
I _x _val ——> 表示 I I xlastAns vallastAns
简单来说就是操作中输入的整数都要异或上一次询问的结果进行解码。
Output
对于每个询问输出回答,每行一个回答。
Sample Input
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
Sample Output
28
2
31
0
14
15
14
27
15
14
HINT
此题作为一个小小的研究来搞吧~做法有很多~不知道这题究竟有多少种做法。
请自觉 O(log2n) O ( l o g 2 n ) ,我故意卡块状链表,块链A了的请受我深情一拜……
A掉的同学请在Discuss里面简要说下自己的做法吧~
原序列长度 <=35000 <= 35000 <script type="math/tex" id="MathJax-Element-4996"><= 35000</script>,插入个数 <=35000 <= 35000 <script type="math/tex" id="MathJax-Element-4997"><= 35000</script>,修改个数 <=70000 <= 70000 <script type="math/tex" id="MathJax-Element-4998"><= 70000</script>,查询个数 <=70000 <= 70000 <script type="math/tex" id="MathJax-Element-4999"><= 70000</script>, 0<= 0 <= 每时每刻的权值 <=70000 <= 70000 <script type="math/tex" id="MathJax-Element-5001"><= 70000</script>。

题外话

这怕不是我写过的最清新(毒瘤)的题目了,毕竟我还没有去做过紫荆花之恋。。码了一个下午和晚上。。

题解

做法:
这题其实做法还是比较明显的。有区间 k k 小值,那么就肯定要一棵权值线段树了。还要待修改的,那么就是很显然的树套树了。外面一棵树用来维护区间,里面一棵树用来维护权值。至于用什么套,splay啊,Treap什么的都可以了。但是splay或者Treap都需要旋转,而旋转之后其父亲关系就会发生变化,这样每次的旋转就会需要一棵新的线段树了(或者线段树合并也是可以的)。然而这样子常数便是大的飞起。这个时候替罪羊树就会发挥他的作用了,替罪羊树只需要在重构的时候再顺便将线段树重构,而重构的次数又是logn的,所以常数自然就是会比splay或者Treap优越许多了。这题算是我写过的第一棵树套树,然而却给下我了不小的收获(阴影)。这题最坑的一点似乎是在于空间的问题。
空间复杂度分析:
替罪羊树的复杂度为 O(n) O ( n ) 的。
线段树如果不修改: log2ni=1n[log2nlog2ni+1j=1(2i1)/2i] ∑ i = 1 l o g 2 n n ∗ [ l o g 2 n − ∑ j = 1 l o g 2 n − i + 1 ( 2 i − 1 ) / 2 i ] ,节点数每个大概150个。
如果线段树修改: 2n+log2n1i=1[17log2nj+21(2i1)/2i]n2 2 ∗ n + ∑ i = 1 l o g 2 n − 1 [ 17 − ∑ 1 l o g 2 n − j + 2 ( 2 i − 1 ) / 2 i ] ∗ n ∗ 2 ,节点数每个大概280个。(不要问我是怎么弄出来的雾
这样子就不得不要写个回收空间了。如果暴力的开点,想必是会炸了。说实话,这个动态回收内存刚开始真的不知道怎么写,后来翻了一下其他的博客,发现最简单的就是直接重载指针的new和delete函数就行了雾。
时间复杂度分析:
最初的构建是 O(n+nlogn) O ( n + n l o g n ) 的,每次插入是 O(log2n) O ( l o g 2 n ) 的(替罪羊树一个 log l o g ,权值线段树一个 log l o g ),修改也是 O(log2n) O ( l o g 2 n ) 的(同插入),查询还是 O(log2n) O ( l o g 2 n ) 的(二分一个 log l o g ,权值线段树一个 log l o g )。所以总的时间复杂度也是 O(qlog2n) O ( q ∗ l o g 2 n ) 的。

AC代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
bool Finish_read;
template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;}
template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');}
template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');}
template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);}
/*================Header Template==============*/
const double alpha=0.8;
const int N=70005;
const int maxn=7e4+500; 
const int range=7e4+500;
#define SIZE(a) (a==NULL?0:a->sz)
int n,a[maxn],m;
int ans=0;
/*==================Define Area================*/
struct SegTreenode {
    static queue<SegTreenode*>bin;//权值线段树的回收节点
    SegTreenode *son[2];//左右儿子
    int cnt;//记录小于等于这个权值的点的个数

    void *operator new(size_t,int nul1=0,SegTreenode *nul2=NULL,SegTreenode *nul3=NULL) {//重载new函数
        static SegTreenode *mempool,*C;
        SegTreenode *tmp;
        if(!bin.empty()) {
            tmp=bin.front();bin.pop();
        }
        else {
            if(C==mempool) {
                C=new SegTreenode[N];
                mempool=C+N;
            }
            tmp=C++;
        }
        tmp->cnt=nul1;
        tmp->son[0]=nul2;
        tmp->son[1]=nul3;
        return tmp;
    }
    void operator delete(void *r) {//重载delete函数
        bin.push(static_cast<SegTreenode*>(r));
    }
    void Insert(int l,int r,int v) {//权值线段树中插入节点
        ++cnt;
        if(l==r) return ;
        int mid=(l+r)>>1;
        if(v<=mid) {
            if(son[0]==NULL) {
                son[0]=new SegTreenode();
            } 
            son[0]->Insert(l,mid,v);
        }
        else {
            if(son[1]==NULL) {
                son[1]=new SegTreenode();
            }
            son[1]->Insert(mid+1,r,v);
        }
    }
    void Delete(int l,int r,int v) {//删除节点
        --cnt;
        if(l==r) return ;
        int mid=(l+r)>>1;
        if(v<=mid) son[0]->Delete(l,mid,v);
        else son[1]->Delete(mid+1,r,v); 
    }
    void Decomposition() {//销毁这颗权值线段树
        if(son[0]!=NULL) son[0]->Decomposition();
        if(son[1]!=NULL) son[1]->Decomposition();
        delete this;
    }
    int Ask(int l,int r,int v) {//权值线段树的查询,查询小于等于v的值有几个
        if(this==NULL) return 0;
        if(l==r) return cnt;
        int mid=(l+r)>>1;
        if(v<=mid) return son[0]->Ask(l,mid,v);
        else return (son[0]?son[0]->cnt:0)+son[1]->Ask(mid+1,r,v);
    }
}; 

struct ScapeTreenode {
    static queue<ScapeTreenode*> bin;//替罪羊树的回收节点
    ScapeTreenode *son[2];//左右儿子
    SegTreenode *root;//替罪羊树这个节点所对应的权值线段树的节点
    int val,sz;//节点对应的权值线段树的右端点权值,节点子树大小

    void *operator new(size_t,int v,ScapeTreenode *ls,ScapeTreenode *rs,SegTreenode *root,int _sz) {//重载new函数
        static ScapeTreenode *mempool,*C;
        ScapeTreenode *tmp;
        if(!bin.empty()) {
            tmp=bin.front();bin.pop();
        }
        else {
            if(C==mempool) {
                C=new ScapeTreenode[N];
                mempool=C+N;
            }
            tmp=C++;
        }
        tmp->val=v;
        tmp->son[0]=ls;tmp->son[1]=rs;
        tmp->root=root;
        tmp->sz=_sz;
        return tmp;
    }
    void operator delete(void *r) {//重载delete函数
        bin.push(static_cast<ScapeTreenode*>(r));
    }
    void Update() {//节点信息更新
        sz=1;
        if(son[0]!=NULL) sz+=son[0]->sz;
        if(son[1]!=NULL) sz+=son[1]->sz;
    }
    int Ask(int k,int v) {//替罪羊树查询区间[1,K]中小于等于v的有几个。
        if(!k) return 0;
        if(SIZE(son[0])>=k) return son[0]->Ask(k,v);
        k-=SIZE(son[0]);
        int tmp=(son[0]?son[0]->root->Ask(0,range,v):0)+(val<=v);
        if(k==1) return tmp;
        return son[1]->Ask(k-1,v)+tmp;
    }
}*root;
queue<ScapeTreenode*> ScapeTreenode::bin;
queue<SegTreenode*> SegTreenode::bin;

ScapeTreenode *Build(int l,int r,int st[]) {//替罪羊树构建与重构
    if(l>r) return NULL;
    int mid=(l+r)>>1;
    SegTreenode *rt=new(0,NULL,NULL)SegTreenode;
    for(int i=l;i<=r;i++) {
        rt->Insert(0,range,st[i]);
    }
    ScapeTreenode *tmp=new(st[mid],Build(l,mid-1,st),Build(mid+1,r,st),rt,r-l+1)ScapeTreenode;
    return tmp;
}

int st[maxn],top;
void Dfs(ScapeTreenode *a) {//中序遍历储存节点
    if(a==NULL) return ;
    Dfs(a->son[0]); 
    st[++top]=a->val;
    Dfs(a->son[1]);
    a->root->Decomposition();
    delete a;
} 

void ReBuild(ScapeTreenode *a) {//重构
    top=0;
    Dfs(a);
    a=Build(1,top,st);
}

bool Bad(ScapeTreenode *a) {//判断是否需要重构
    if(a->son[0]!=NULL) {
        if((double)a->son[0]->sz>=(double)a->sz*alpha) return 1;
    }
    if(a->son[1]!=NULL) {
        if((double)a->son[1]->sz>=(double)a->sz*alpha) return 1;
    }
    return 0;
}

ScapeTreenode *reb;//记录重构的节点
void Insert(ScapeTreenode *&a,int k,int v) {//替罪羊树的插入
    if(a==NULL) {
        SegTreenode *root=new SegTreenode();
        root->Insert(0,range,v);
        a=new(v,NULL,NULL,root,1)ScapeTreenode;
        return ;
    }
    a->root->Insert(0,range,v);
    int _sz=SIZE(a->son[0]);            
    if(k<=_sz) Insert(a->son[0],k,v);
    else Insert(a->son[1],k-_sz-1,v);
    a->Update();
    if(Bad(a)) reb=a;
}

void Modify(ScapeTreenode *a,int k,int v) {//替罪羊树的权值修改
    static int old;
    if(k<=SIZE(a->son[0])) Modify(a->son[0],k,v);
    else if((k-=SIZE(a->son[0]))==1) {
        old=a->val;
        a->val=v;
    }
    else Modify(a->son[1],k-1,v);
    a->root->Delete(0,range,old);
    a->root->Insert(0,range,v);
}

int Cal(int l,int r,int ans) {//答案的计算
    return root->Ask(r,ans)-root->Ask(l-1,ans);
}

int Solve(int x,int y,int k) {//二分答案,查询是否满足
    int L=0,R=range,res=-1;
    while(L<=R) {
        int mid=(L+R)>>1;
        if(Cal(x,y,mid)>=k) {
            R=mid-1;
            res=mid;
        }
        else L=mid+1;
    }
    return res;
}

int main() {
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    root=Build(1,n,a);
    read(m); 
    while(m--) {
        char opt[2];
        int x,y,val;
        scanf("%s",opt);
        if(opt[0]=='Q') {
            read(x);read(y);read(val);
            x^=ans;y^=ans;val^=ans;
            ans=Solve(x,y,val);
            printf("%d\n",ans);
        }
        if(opt[0]=='M') {
            read(x);read(val);
            x^=ans;val^=ans;
            Modify(root,x,val);
        }
        if(opt[0]=='I') {
            read(x);read(val);
            x^=ans;val^=ans;
            reb=NULL;
            Insert(root,x-1,val);
            if(reb!=NULL) {
                ReBuild(reb);
            }
        }
    }
    return 0;
}
/*
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
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值