BZOJ3065 带插入区间K小值 || 洛谷P4278

这是一道让我崩溃的题......

然鹅洛谷上时限被改然后只有20分......好像所有人都被卡了(雾)

由于替罪羊树不是依靠旋转操作而是依靠暴力重构的方式维护树的平衡,所以我们可以考虑使用替罪羊树套区间线段树的方式来过去。

附上极其丑陋的代码,代码里面有解释......

#include <cstdio>
#include <algorithm>
#include <vector>
#include <cctype>
using namespace std;

const double apl = 0.75;

    struct seg_node { // 线段树节点 
        seg_node *ls, *rs;
        int val;
    }gt[10000000], *tot, *Null;
    vector< seg_node* > zt;
    struct SG_node { // 替罪羊树节点 
        SG_node *ls, *rs;
        int pos, size;
        seg_node *root; // 该节点的线段树的根 
        bool isbad() { return max(ls->size, rs->size) > this->size * apl; }
    }tc[70010], *bc[70010], *cnt, *null, *root;
    int bc_top;
    
    seg_node *new_seg_node (){ // 新的线段树的节点 
        seg_node *k; if( zt.size()) k = zt.back(), zt.pop_back();
        else k = ++ tot;
        k->ls = k->rs = Null; k->val = 0; return k;
    }
    SG_node *new_SG_node (int val) { // 新的替罪羊树的节点 
        SG_node *k = bc_top ? bc[bc_top --] : ++ cnt;
        k->pos = val; k->size = 1;
        k->ls = k->rs = null; k->root = Null; return k;
    }
    inline void init() { // 初始化 
        tot = gt; cnt = tc; Null = tot; null = cnt;
        null->ls = null->rs = null; Null->ls = Null->rs = Null;
        Null->val = 0; null->pos = null->size = 0; 
        root = null; null->root = Null;
    }
    #define mid ( l + r >> 1)
    inline void updata(seg_node *&x, int l, int r, int L, int o) { // 更新区间线段树 
        if( x == Null) x = new_seg_node(); 
        if( l == r) { x->val += o; if( x->val == 0) zt.push_back(x), x = Null; return ; }
        if( L <= mid) updata(x->ls, l, mid, L, o);
        else updata(x->rs, mid+1, r, L, o);
        x->val = x->ls->val + x->rs->val;
        if( x->val == 0) zt.push_back(x), x = Null;
    }
/* ----------------------------------------------------------------------------------------------
    inline void updata(seg_node *&x, int l, int r, int L, int o) { // 更新区间线段树 
        if( x == Null) x = new_seg_node(); 
        if( l == r) { x->val += o; return ; }
        if( L <= mid) updata(x->ls, l, mid, L, o);
        else updata(x->rs, mid+1, r, L, o);
        x->val = x->ls->val + x->rs->val;
        if( x->ls->val == 0 && x->ls != Null) zt.push_back(x->ls), x->ls = Null; 
        if( x->rs->val == 0 && x->rs != Null) zt.push_back(x->rs), x->rs = Null;
    }比较下上下的区别,下面的在bzoj A了 但在洛谷RE了一整页......不就少回收了一个节点吗,为什么RE 
-----------------------------------------------------------------------------------------------*/
    int query(seg_node *x, int l, int r, int L) { // 查询区间线段树上小于L的数的个数 
        if( r < L || x == Null) return x->val;
        if( L <= mid) return query(x->ls, l, mid, L);
        else return x->ls->val + query(x->rs, mid+1, r, L);
    }
    inline void GG(seg_node *x) { if( x == Null) return ; zt.push_back(x); GG( x->ls); GG( x->rs); } // 回收线段树节点  
    int p, dis[70010];
    inline void rca(SG_node *x) { if( x == null) return ; rca(x->ls); dis[++p] = x->pos; bc[++bc_top] = x; GG(x->root); rca(x->rs); } // 回收替罪羊树节点 
    
    inline void rbuild(SG_node *&x, int l, int r) { // 暴力重构替罪羊树 
        if( l > r) return ;
        x = new_SG_node(dis[mid]); x->size = r - l + 1;
        rbuild(x->ls, l, mid-1); rbuild(x->rs, mid+1, r);
        for ( int i = l; i <= r; i ++) // 更新当前线段树 
            updata(x->root, 0, 70010, dis[i], 1);
    }
    
    inline void insert(SG_node *&x, int id, int val) { // 替罪羊树上插入新节点 
        if( x == null) { x = new_SG_node(val); updata(x->root, 0, 70010, val, 1); return ; }
        if( id > x->ls->size+1) insert(x->rs, id-x->ls->size-1, val);
        else insert(x->ls, id, val);
        x->size ++; updata(x->root, 0, 70010, val, 1);
        if( x->isbad()) p = 0, rca(x), rbuild(x, 1, p);
    }
    
    int change(SG_node *&x, int val, int id) { // 更改某一位置上的值 
        updata(x->root, 0, 70010, val, 1); int c;
        if( id == x->ls->size+1) c = x->pos, x->pos = val;
        else if( id > x->ls->size + 1) c = change(x->rs, val, id - x->ls->size - 1);
        else c = change(x->ls, val, id);
        updata(x->root, 0, 70010, c, -1);
        return c;
    }

vector < seg_node * > sc1; // 满足该节点以及其子节点都在询问区间【L , R】 内的线段树的根 
vector < int > sc2; // 不满足 其子节点都在询问区间但该节点在询问区间的值 
inline void search(SG_node *x, int l, int r) { // 查找询问区间 
    if( x == null || l > r) return ;
    if( l == 1 && r == x->size) { sc1.push_back(x->root); return ; }
    int k = x->ls->size + 1;
    if( l <= k && r >= k) {
        sc2.push_back(x->pos); search(x->ls, l, x->ls->size); search(x->rs, 1, r-k);
    }
    if( r < k) search(x->ls, l, r); if( l > k) search(x->rs, l-k, r-k);
}

int modify(int L, int R, int k) { // 二分查找答案 
    int l = 0, r = 70010; search(root, L, R);
    int t1 = sc1.size(), t2 = sc2.size();
    sort(sc2.begin(), sc2.end());
    while( l < r) {
        int ans = 0;
        for ( int i = 0; i < t1; i ++) ans += query(sc1[i], 0, 70010, mid);
        for ( int i = 0; i < t2; i ++) if( sc2[i] < mid) ans ++; else break;
        if( ans >= k) r = mid;
        else l = mid+1;
    }
    sc1.clear(); sc2.clear(); return l - 1; // 为什么是  l - 1 自己YY 
}

inline void G(int &x) { // 快读 
    x = 0; int f = 1; char o = getchar(); for ( ; !isdigit(o); o = getchar()) if( o == '-') f = -1;
    for ( ; isdigit(o); o = getchar()) x = ( x << 1) + ( x << 3) + ( o & 15); x *= f;
}

int n, q, lastans;
char s[10];
int main() {
    init(); G(n);
    for ( int i = 1, a; i <= n; i ++) G(a), dis[i] = a; rbuild(root, 1, n);
    G(q);
    for ( int i = 1, x, y, z; i <= q; i ++) {
        scanf("%s", s);
        switch (s[0]) {
            case 'Q' : G(x); G(y); G(z); x^=lastans; y^=lastans; z^=lastans; printf("%d\n", lastans = modify(x, y, z)); break;
            case 'M' : G(x); G(y); x^=lastans; y^=lastans; change(root, y, x); break;
            case 'I' : G(x); G(y); x^=lastans; y^=lastans; insert(root, x, y); break;
        }
    }
}
View Code

自从做了这道题,我发现我会用指针了......

感觉......指针 == 数组 ......


 

转载于:https://www.cnblogs.com/miecoku/p/9171737.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值