其他分享
首页 > 其他分享> > 6.29 $\text{Data Structures}$

6.29 $\text{Data Structures}$

作者:互联网

\(\text{Date: 6.29}\)

\(\text{Summary - Better Data Structures}\)

\(\text{Contents: Segment Tree, (advanced)B-I-T}\)

\(\text{I - Segment Tree}\)

一些信息之所以能用线段树维护,是因为这些信息具有合并性(已知\([l,mid]\)和\([mid+1,r]\)的信息,能否整合推导出\([l,r]\)的信息),即可以被pushup。题目要求区间修改时需考虑是否可以及如何设置懒标记tag[p]

细节与注意事项:(以Luogu P1471为例)

\(1.\) 打好\(\#\rm define\)减码量

#include <bits/stdc++.h>
#define _ read()
#define LL long long
#define lsp p << 1
#define rsp p << 1 | 1
#define MID mid = (t[p].l + t[p].r) >> 1
#define lowbit(x) (x & -x)
using namespace std;
const int N = 1.2 * 1e6 + 5;

\(2.\) pushup较复杂时建议用结构体传递参数(或者直接 T operator + ... const !)

inline T pushup(T L, T R) {
    T res;
    res.l = L.l, res.r = R.r;
    res.sum = L.sum + R.sum;
    res.val = L.val + R.val;
    return res;
}

// pushup like this: (if you don't like it, just #define)
// t[p] = pushup(t[lsp], t[rsp]);

\(3.\) pushdown记得分步写(将更新区间和传递tag的操作分开),更简洁(F(x)update中也能用到)

inline void Fx(int p, double d) { // 用参数 d (懒标记)更新节点 p 
    double Sum = t[p].sum; // 注意变量覆盖问题
    t[p].sum += d * Len(p);
    t[p].val += 2.0 * d * Sum + d * d * Len(p);
}

inline void pushdown(int p) {
    Fx(lsp, tag[p]);
    Fx(rsp, tag[p]);
    tag[lsp] += tag[p];
    tag[rsp] += tag[p];
    tag[p] = 0.0;
}

\(4.\) query时将函数类型设为结构体类型,省去不必要的麻烦

T query(int p, int l, int r) {
    if(l <= t[p].l && t[p].r <= r) return t[p];
    pushdown(p); int MID;
    if(r <= mid) return query(lsp, l ,r);
    else if(l > mid) return query(rsp, l, r);
    else return pushup(query(lsp, l, r), query(rsp, l, r));
}

\(\large\to\text{A better B-I-T}\leftarrow\)
A Link

A link

\(\rm Ex.\) 哎哟,这树状数组针不戳: \(\text{advanced B-I-T}\)

树状数组维护不可差分信息

使用树状数组在单步操作\(O(\log^2n)\)的时间复杂度内维护单点修改 + 区间最值查询。(感觉没有那么那么的实用,但是码量是真\(^{\rm TM}\)的少啊)

然而,下面这个东西就是非常强大了:

平衡树 \(\overset{\text{低 配 版}}{\leftarrow}\) 树状数组

限制:因为树状数组建立在值域上,而离散化会导致整个算法变为离线,所以遇到什么值域为\(10^9\)或有负数的树状数组就挂了。

update: 其实负数并不会挂,将整体值域+N即可。

还有,平衡树版的树状数组无法实现反转。

代码仍然是真\(^{\rm TM}\)的短。

总代码:

#include <bits/stdc++.h>
#define _ read()
#define lowbit(x) (x & -x)
using namespace std;
const int N = 1e7 + 5;

inline void File() {
    freopen("in.txt", "r", stdin);
    freopen("Ans.txt", "w", stdout);
}

inline int read() {
    int x = 0, w = 0; char ch = getchar();
    while(!isdigit(ch)) { w |= ch == 45; ch = getchar(); }
    while(isdigit(ch)) { x = (x << 1) + (x << 3) + (ch ^ 48); ch = getchar(); }
    return w ? -x : x;
}

namespace Ax { // the common template
    int n, a[N], c[N];

    inline void update(int x, int d) {
        for(int i = x; i <= n; i += lowbit(i)) c[i] += d;
    }

    inline int query(int x) {
        int res = 0;
        for(int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }
    // You can also use the B-I-T as a BUCKET.
}

namespace Bx { // 单点修改 + 区间最值 单次O(log^2 n)
    int n, a[N], mx[N];

    inline void update(int x, int d) { // 必须同时更新a[i]的值
        a[x] = d;
        for(int i = x; i <= n; i += lowbit(i)) {
            mx[i] = a[i]; int t = lowbit(i);
            for(int j = 1; j < t; j <<= 1) mx[i] = max(mx[i], mx[i - j]);
        }
    }

    inline int query(int l, int r) { // 必须同时更新a[i]的值
        int Len = r - l + 1, res = a[r];
        while(Len && r) {
            if(Len >= lowbit(r)) {
                res = max(res, mx[r]);
                Len -= lowbit(r);
                r -= lowbit(r);
            }
            else {
                res = max(res, a[r]);
                Len--, r--;
            }
        }
        return res;
    }
    /*
    int g;
        if(opt == 1) {
            int x = _, y = _;
            g = a[x];
            update(x, y);
        }
    */
}

namespace Cx { // BIT as a Treap
    int n, a[N], c[N];
    int Mx, MxLog;
    
    inline void init() {
        for(int i = 1; i <= n; i++) Mx = max(Mx, a[i]);
        MxLog = log2(Mx) + 1;
    }

    inline void update(int x, int d) {
        for(int i = x; i <= n; i += lowbit(i)) c[i] += d;
    }

    inline int query(int x) {
        int res = 0;
        for(int i = x; i; i -= lowbit(i)) res += c[i];
        return res;
    }

    inline void Ins(int x) {
        update(x, 1);
    }

    inline void Del(int x) {
        update(x, -1);
    }

    inline int Rank(int x) {
        return query(x - 1) + 1;
    }

    int kth(int k) {
        int ans = 0, cnt = 0;
        for(int i = 30; i >= 0; i--) {//i实际上为log2(maxn),maxn是a数组中的最大值
            ans += (1 << i);
            if(ans > Mx || cnt + c[ans] >= k) ans -= (1 << i);//>=k是为了防止有重复元素
            else cnt += c[ans];
        }
        return ans + 1;//如果找不到则返回n+1
    }

    inline int pre(int x) {
        return kth(Rank(x) - 1);
    }

    inline int nxt(int x) {
        return kth(Rank(x + 1));
    }
}

using namespace Cx;

signed main() {
    File();
    Mx = n = 1e7 + 5;
    //MxLog = log2(Mx) + 1;
    int T, opt, x;
    for(T = read(); T; --T) {
        opt = read(); x = read();
        if(opt == 1) Ins(x), Mx = max(Mx, x);
        else if(opt == 2) Del(x);
        else if(opt == 3) printf("%d\n", Rank(x));
        else if(opt == 4) printf("%d\n", kth(x));
        else if(opt == 5) {
            printf("%d\n", pre(x));
        }
        else {
            printf("%d\n", nxt(x));
        }
    }
    return 0;
}

\(\rm II - Splay\)

\(\to\text{Link}\leftarrow\)

这个东西真的很难调!细节非常的多,操作也非常的灵活。
但是,可以总结几个有概率出错的地方:
\(1.\rm rotate\)

	void rotate(int x) {
    	int f = t[x].fa, ff = t[f].fa, chk = get(x), w = t[x].ch[chk ^ 1];
    	t[f].ch[chk] = w; t[w].fa = f; // deal with ch[x][chk ^ 1]
    	t[ff].ch[get(f)] = x; t[x].fa = ff; // deal with fa[fa[x]]
    	t[x].ch[chk ^ 1] = f; t[f].fa = x; // between x & fa[x]
    	maintain(f), maintain(x);
	}

\(2.\rm Splay\)

    void splay(int x, int g = 0) {
        while(t[x].fa != g) {
            int f = t[x].fa, ff = t[f].fa;
            if(ff != g) rotate(get(x) == get(f) ? f : x);
            rotate(x);
        }
        if(!g) rt = x;
    }

\(3.\) pre(x)nxt(x) 记得加:

	if(t[rt].val < v) // in pre 
	if(t[rt].val > v) // in nxt

注意 \(\text{kth(x), pre(x), nxt(x)}\) 返回的是节点编号

然后就是一个查找特定节点的或特定区间的技巧:

int L = pre(v), R = nxt(v);
splay(L); splay(R, L);
int x = t[R].ch[0];

//或者
int L = kth(l), r = kth(r + 2);
splay(L); splay(R, L);
// t[R].ch[0] -> The interval of [l, r]

这样,\(rt=L,ch[rt][1]=R,ch[ch[rt][1]][0]\) 就是我们想要进行操作的位置。

在处理区间问题时,要多写一个 build(int l, int r, int Fa)。在建立新节点的时候(在build中或者是ins中)还有写rotate一定要特别小心。

(好像在写区间操作的时候,大家一般习惯\([1,n]\to[2,n+1]\)?)

还有在处理带tag的问题时一定要及时maintainpushdown

\(\rm Splay\) 中常用函数的功能:

标签:ch,Data,int,text,fa,res,rm,Structures
来源: https://www.cnblogs.com/Doge297778/p/16422961.html