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

题目大意:带插入,单点修改的区间k小值在线查询。

思路:本年度做过最酸爽的题。

树套树的本质是一个外层不会动的树来套一个内层会动(或不会动)的树。两个树的时间复杂度相乘也就是差不多O(nlog^2n)左右。但是众所周知,高级数据结构经常会伴有庞大的常数,所以一般来说树套树的常数也不会小到哪去。所以在做这种题的时候先不要考虑常数的问题。。。

为什么要用替罪羊树呢?因为一般的平衡树都是会动的,这就很难办了。外层的树动了之后,内层的树肯定也是会动的。很显然,一般的二叉平衡树会经常会旋转,这样在动外层的树那么时间复杂度就会飞起。所以就需要一种不会动(或很少动)的二叉平衡树来做外层的树。

这样的树有两种:朝鲜树和替罪羊树。其中朝鲜树的时间复杂度是大概O(√n)的,而替罪羊树是O(logn)的。为什么朝鲜树的均摊时间是O(√n)呢,因为朝鲜树的思想是当树的高度超过√n是就暴力重建整棵树。所以所有操作的均摊时间就是O(√n)。

替罪羊树相对来说就比较优越一些了。一般来说替罪羊树很少直接暴力重建整棵树。在每次向替罪羊树中插入东西的时候,就查看经过的节点中有没有子树的size太大的节点,如果有的话就直接重建一个子树,保证了树的高度是logn左右,均摊时间复杂度也就减下来了。

重建一棵子树也十分简单,把这个子树的中序遍历记录下来,然后让它变得平衡就行了。然后要记得经过的所有节点都要回收内存,~不然内存会飞起~,用一个厉害一点的垃圾收集器,内存池也别静态开了,没敢,直接全都动态。当然,外层替罪羊树的内存回收了,内层的线段树的内存也要回收,~不然内存会飞起~。

这个题的查询过程时间复杂度O(nlong^3n),其余操作均摊O(nlong^2n)。推荐你们去看看vfk的代码,简直dio……

CODE:

#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define MAX 70010
#define RANGE 70010
using namespace std;
#define SIZE(a) ((a) == NULL ? 0:(a)->size)
const double alpha = 0.8;
const int L = 1 << 15;

int cnt,src[MAX],asks;

struct SegTree{
    static queue<SegTree *> bin;
    SegTree *son[2];
    int cnt;

    void *operator new(size_t,int _ = 0,SegTree *__ = NULL,SegTree *___ = NULL) {
        static SegTree *mempool,*C;
        SegTree *re;
        if(!bin.empty()) {
            re = bin.front(); bin.pop();
        }
        else {
            if(C == mempool)
                C = new SegTree[L],mempool = C + L;
            re = C++;
        }
        re->cnt = _;
        re->son[0] = __,re->son[1] = ___;
        return re;
    }
    void operator delete(void *r) {
        bin.push(static_cast<SegTree *>(r));
    }

    void Insert(int l,int r,int x) {
        ++cnt;
        if(l == r)  return ;
        int mid = (l + r) >> 1;
        if(x <= mid) {
            if(son[0] == NULL)
                son[0] = new SegTree();
            son[0]->Insert(l,mid,x);
        }
        else {
            if(son[1] == NULL)
                son[1] = new SegTree();
            son[1]->Insert(mid + 1,r,x);
        }
    }
    void Delete(int l,int r,int x) {
        --cnt;
        if(l == r)  return ;
        int mid = (l + r) >> 1;
        if(x <= mid) son[0]->Delete(l,mid,x);
        else    son[1]->Delete(mid + 1,r,x);
    }
    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 x) {
    	if(this == NULL) return 0;
    	if(l == r)	return cnt;
        int mid = (l + r) >> 1;
        if(x <= mid) return son[0]->Ask(l,mid,x);
        return (son[0] ? son[0]->cnt:0) + son[1]->Ask(mid + 1,r,x);
    }
};

struct ScapeTree{
    static queue<ScapeTree *> bin;
    ScapeTree *son[2];
    SegTree *root;
    int val,size;

    void *operator new(size_t,int _,ScapeTree *__,ScapeTree *___,SegTree *____,int _size) {
        static ScapeTree *mempool,*C;
        ScapeTree *re;
        if(!bin.empty()) {
            re = bin.front(); bin.pop();
        }
        else {
            if(C == mempool)
                C = new ScapeTree[L],mempool = C + L;
            re = C++;
        }
        re->val = _;
        re->son[0] = __,re->son[1] = ___;
        re->root = ____;
        re->size = _size;
        return re;
    }
    void operator delete(void *r) {
        bin.push(static_cast<ScapeTree *>(r));
    }
    void Maintain() {
        size = 1;
        if(son[0] != NULL)  size += son[0]->size;
        if(son[1] != NULL)  size += son[1]->size;
    }
    int Ask(int k,int _val) {
    	if(!k) return 0;
        if(SIZE(son[0]) >= k) return son[0]->Ask(k,_val);
        k -= SIZE(son[0]);
        int re = (son[0] ? son[0]->root->Ask(0,RANGE,_val):0) + (val <= _val);
        if(k == 1)  return re;
        return son[1]->Ask(k - 1,_val) + re;
    }
}*root;
queue<ScapeTree *> ScapeTree :: bin;
queue<SegTree *> SegTree :: bin;

ScapeTree *BuildScapeTree(int l,int r,int arr[])
{
    if(l > r)    return NULL;
    int mid = (l + r) >> 1;
    SegTree *root = new (0,NULL,NULL)SegTree;
    for(int i = l; i <= r; ++i)
        root->Insert(0,RANGE,arr[i]);
    ScapeTree *re = new (arr[mid],BuildScapeTree(l,mid - 1,arr),BuildScapeTree(mid + 1,r,arr),root,r - l + 1)ScapeTree;
    return re;
}

int temp[MAX],top;

void DFS(ScapeTree *a)
{
    if(a == NULL)   return ;
    DFS(a->son[0]);
    temp[++top] = a->val;
    DFS(a->son[1]);
    a->root->Decomposition();
    delete a;
}

void Rebuild(ScapeTree *&a)
{
    top = 0;
    DFS(a);
    a = BuildScapeTree(1,top,temp);
}

bool Check(ScapeTree *a)
{
    if(a->son[0] != NULL)
        if((double)a->son[0]->size / a->size > alpha)
            return false;
    if(a->son[1] != NULL)
        if((double)a->son[1]->size / a->size > alpha)
            return false;
    return true;
}

ScapeTree *will_rebuild;

void Insert(ScapeTree *&a,int k,int val)
{
    if(a == NULL) {
        SegTree *root = new SegTree();
        root->Insert(0,RANGE,val);
        a = new (val,NULL,NULL,root,1)ScapeTree;
        return ;
    }
    a->root->Insert(0,RANGE,val);
    int temp = SIZE(a->son[0]);
    if(k <= temp)	Insert(a->son[0],k,val);
	else	Insert(a->son[1],k - temp - 1,val);
	a->Maintain();

    if(!Check(a))   will_rebuild = a;
}

void Modify(ScapeTree *a,int k,int val)
{
    static int old;
    if(k <= SIZE(a->son[0]))
        Modify(a->son[0],k,val);
    else if((k -= SIZE(a->son[0])) == 1) {
        old = a->val;
    	a->val = val;
	}
    else
        Modify(a->son[1],k - 1,val);
    a->root->Delete(0,RANGE,old);
    a->root->Insert(0,RANGE,val);
}

inline int Judge(int l,int r,int ans)
{
    return root->Ask(r,ans) - root->Ask(l - 1,ans);
}

inline int Query(int x,int y,int k)
{
    int l = 0,r = RANGE,re = -1;
    while(l <= r) {
        int mid = (l + r) >> 1;
        if(Judge(x,y,mid) >= k)
            r = mid - 1,re = mid;
        else    l = mid + 1;
    }
    return re;
}

char c[10];

int main()
{
    cin >> cnt;
    for(int i = 1; i <= cnt; ++i)
        scanf("%d",&src[i]);
    root = BuildScapeTree(1,cnt,src);
    cin >> asks;
    int last_ans = 0;
    for(int x,y,z,i = 1; i <= asks; ++i) {
        scanf("%s%d%d",c,&x,&y);
        x ^= last_ans;
        y ^= last_ans;
        if(c[0] == 'Q') {
            scanf("%d",&z);
            z ^= last_ans;
            printf("%d\n",last_ans = Query(x,y,z));
        }
        else if(c[0] == 'M')
            Modify(root,x,y);
        else {
            will_rebuild = NULL;
            Insert(root,x - 1,y);
            if(will_rebuild != NULL)
                Rebuild(will_rebuild);
        }
    }
    return 0;
}

时间: 2024-09-30 10:44:20

BZOJ 3065 带插入区间K小值 替罪羊树套线段树的相关文章

bzoj 3065: 带插入区间K小值 替罪羊树 &amp;&amp; AC300

3065: 带插入区间K小值 Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 1062  Solved: 253[Submit][Status] Description 从 前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一 下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少. 这可难不倒伏特,

bzoj 3065 带插入区间k小值

替罪羊树套权值线段树. 计数式垃圾回收. 复杂度nlog2^n. 写了半个冬令营. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<vector> 5 #include<algorithm> 6 #define N 10000005 7 #define alpha 0.75 8 using namespace std; 9 inline int read

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

题意:带插入.修改的区间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]

【bzoj3065】带插入区间K小值 替罪羊树套权值线段树

题目描述 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少.这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问.这时伏特发现有些跳蚤跳久了弹跳力会有变化,有的会增大,有的会减少.这可难不倒伏特,他在脑袋里使用树状数组套线段树的方法水掉了跳蚤国王的询问.(orz 主席

[BZOJ3065]带插入区间K小值 解题报告 替罪羊树+值域线段树

刚了一天的题终于切掉了,数据结构题的代码真**难调,这是我做过的第一道树套树题,做完后感觉对树套树都有阴影了......下面写一下做题记录. Portal Gun:[BZOJ3065]带插入区间k小值. 这道题的题面其实都提醒怎么做了,维护区间k小值用值域线段树,但要维护一个插入操作,树状数组套主席树也用不了,那么这道题还剩下平衡树可以搞,那就上平衡树吧. 我这里的做法,因为要维护序列的顺序,所以我这里用到替罪羊树套值域线段树:我们在替罪羊树的每个节点都套一颗值域线段树,记录以该节点为根的子树的

Bzoj3065 带插入区间K小值

Time Limit: 60 Sec  Memory Limit: 512 MBSubmit: 3436  Solved: 1103 Description 从前有n只跳蚤排成一行做早操,每只跳蚤都有自己的一个弹跳力a[i].跳蚤国王看着这些跳蚤国欣欣向荣的情景,感到非常高兴.这时跳蚤国王决定理性愉悦一下,查询区间k小值.他每次向它的随从伏特提出这样的问题: 从左往右第x个到第y个跳蚤中,a[i]第k小的值是多少.这可难不倒伏特,他在脑袋里使用函数式线段树前缀和的方法水掉了跳蚤国王的询问.这时伏

[bzoj3065] 带插入区间第k小值 [重量平衡树套线段树]

题面 传送门 思路 发现强制在线了...... 本来可以树套树解决的问题,现在外层不能使用线段树了,拿什么替代呢? 我们需要一种支持单点插入.下套数据结构.数据结构上传合并复杂度最多单log,不能旋转的数据结构 这不是摆明了用重量平衡树吗? 我选了替罪羊树作为上层结构,下面套了一棵线段树,就做完了 查询的时候把替罪羊树上对应的log个区间提取出来,一起在底层权值线段树上二分即可 详见代码注释 Code #include<iostream> #include<cstdio> #inc

bzoj3065带插入区间K小值

这题其实好像很难,但是听werkeytom_ftd说可以用块链水,于是就很开心地去打了个块状链表套主席树,插入操作就直接插到一个块中,注意如果块的大小2*block就将块分开,注意每一个修改或插入都要修改后继的状态,贴代码: #include<iostream> #include<algorithm> #include<cstdio> #include<cmath> #include<cstring> #define fo(i,a,b) for(

【块状链表】【权值分块】bzoj3065 带插入区间K小值

显然是块状链表的经典题.但是经典做法的复杂度是O(n*sqrt(n)*log(n)*sqrt(log(n)))的,出题人明确说了会卡掉. 于是我们考虑每个块内记录前n个块的权值分块. 查询的时候差分什么的,复杂度就是O(n*sqrt(n))的了. 插入的时候为了防止块过大,要考虑裂块(细节较多). 感谢bzoj提供O2,我的STL块链才能通过(list+vector). #include<cstdio> #include<list> #include<vector> #