高级数据结构part1

目录

前置知识(提高)

基础方法

  1. 前缀和

  2. 差分

    • 区间加同一个数,查询单点
    • 区间加等差数列,查询单点
    • 区间加同一个数,查询区间和
  3. 离散化

  4. 离线

  5. 二分

  6. 倍增

  7. 双指针

  8. 标记永久化

  9. 反转时间:插入变删除,删除变插入

数据结构

  1. 线段树

线段树(一种分治树,取序列中点进行分治,深度约\(logn\))

相关问题:

  1. 线段树上二分(单侧二分,双侧二分)

把当前 k 与左儿子比较

  1. 动态开点线段树

不建树,修改操作用到的点再创建,查询操作用到了,直接计算出来

适用于无法离散化的情况

空间 \(O(nlogw)\)

  1. 树状数组

树状数组(BIT,自底而上把线段树的右儿子删除)。一个节点\(t[i]\)管辖\(a[i-lowbit(i)+1,..i]\)

相当于一半的线段树,更局限,但是更短更快。

  1. ST表

RMQ问题,\(O(nlogn)\)初始化,\(O(1)\)查询。

  1. 单调队列

单调队列(常结合双指针使用,而不一定要二分)。

  1. 单调栈

单调栈(给一个序列\(a\),对每个下标\(i\),求出\(a_i\)之后第一个大于\(a_i\)的元素的下标,即以\(a_i\)为最大值的极大区间。这个用单调栈可以\(O(n)\)求出。

  1. 并查集

并查集(DSU),路径压缩后时间\(O(logn)\),尽管按秩合并也是这个复杂度,但是常数大。另有带权并查集,可维护点权差的传递,可以利用取模后的权值差来分类。同时并查集可以扩域,维护复杂的信息。

其他

LCA:

  • O(n)~O(nlogn) : 树剖
  • O(nlogn)~O(1) : 括号序->RMQ

DFS序:方便地维护子树信息

平衡树相关

名次树(Treap)

  • 插入\(x\)
  • 删除\(x\)
  • 查询小于\(x\)的数的个数
  • 查询第\(k\)小的数

\(1\le m, x\le 2\times 10^5\)

二叉搜索树(BST)

中序遍历排列关键字得到的序列单调不降的一种树。

平衡树(平衡二叉搜索树)

是深度\(O(logn)\)的二叉搜索树(并不严格\(logn\),但是近似or期望\(logn\))

Splay、Treap etc都是平衡树

Treap Code

    namespace Treap {
    struct Node {
        int ls, rs;
        int v, k, c, s;
    }t[MAXN];
    int cnt, rt;
    void upd(int x) {t[x].s = t[t[x].ls].s + t[t[x].rs].s + t[x].c;}
    void lturn(int& x) {int s = t[x].rs; t[x].rs = t[s].ls; upd(x); t[s].ls = x; upd(s); x = s;}
    void rturn(int& x) {int s = t[x].ls; t[x].ls = t[s].rs; upd(x); t[s].rs = x; upd(s); x = s;}
    void ins(int v, int& x) {
        if(!x) t[x = ++cnt] = (Node){0, 0, v, rand(), 1, 1};
        else if(t[x].v == v) t[x].c++, upd(x);
        else if(t[x].v > v)
            ins(v, t[x].ls), t[t[x].ls].k < t[x].k ? rturn(x) : upd(x);
        else 
            ins(v, t[x].rs), t[t[x].rs].k < t[x].k ? lturn(x) : upd(x);
    }
    void del(int v, int& x) {
        if(t[x].v > v) del(v, t[x].ls), upd(x);
        else if(t[x].v < v) del(v, t[x].rs), upd(x);
        else if(t[x].c > 1) --t[x].c, upd(x);
        else if(!t[x].ls) x = t[x].rs;
        else if(!t[x].rs) x = t[x].ls;
        else if(t[t[x].ls].k < t[t[x].rs].k)
            rturn(x), del(v, t[x].rs), upd(x);
        else
            lturn(x), del(v, t[x].ls), upd(x);
    }
    int rk(int v, int x) {
        if(t[x].v == v) return t[t[x].ls].s + 1;
        else if(t[x].v > v) return rk(v, t[x].ls);
        else return rk(v, t[x].rs) + t[t[x].ls].s + t[x].c;
    }
    int kth(int k, int x) {
        if(k <= t[t[x].ls].s) return kth(k, t[x].ls);
        else if(k <= t[t[x].ls].s + t[x].c) return t[x].v;
        else return kth(k - t[t[x].ls].s - t[x].c, t[x].rs);
    }
    int pre(int v, int x) {
        int now = x, ans;
        while(now) {
            if(t[now].v < v) ans = now, now = t[now].rs;
            else now = t[now].ls;
        }
        return t[ans].v;
    }
    int nxt(int v, int x) {
        int now = x, ans;
        while(now) {
            if(t[now].v > v) ans = now, now = t[now].ls;
            else now = t[now].rs;
        }
        return t[ans].v;
    }
}
using namespace Treap;
/*
注意事项:这个Treap可以适用于空树
*/
    

维护序列(Splay,FHQTreap)

  • 在指定位置插入一个数
  • 删除指定位置的数
  • 区间染色
  • 翻转区间
  • 求区间和
  • 求最大子段和

Splay

均摊\(O(nlogn)\),某次操作的复杂度可能达到\(O(n)\)

常数大

建议:找一个lct的板子学。

Splay Code

const int MAXN = 5e5 + 5;
const int INF = 0x3f3f3f3f;
namespace Splay {
    #define ls ch[0]
    #define rs ch[1]
    #define L e[x].ls
    #define R e[x].rs
    struct Node {
        int fa, ch[2];
        int v, s, sum, lv, rv, mv;
        int tag, rev;
    }e[MAXN];
    int cnt, rt;
    queue que;
    int getid() {
        int ret;
        if(!que.empty()) ret = que.front(), que.pop(); 
        else ret = ++cnt;
        return ret;
    }
    void upd(int x) {
        e[x].sum = e[L].sum + e[R].sum + e[x].v;
        e[x].s = e[L].s + e[R].s + 1;
        e[x].lv = max(e[L].lv, e[L].sum + e[x].v + e[R].lv);
        e[x].rv = max(e[R].rv, e[R].sum + e[x].v + e[L].rv);
        e[x].mv = max(max(e[L].mv, e[R].mv), e[L].rv + e[x].v + e[R].lv);
    }
    void psdmdy(int x, int c) {
        e[x].v = c; e[x].sum = c * e[x].s;
        e[x].mv = max(e[x].v, e[x].sum);
        e[x].lv = e[x].rv = max(0, e[x].sum); 
        e[x].tag = c;
    }
    void psdrev(int x) {
        e[x].rev ^= 1;
        swap(e[x].lv, e[x].rv);
        swap(L, R);
    }
    void psd(int x) {
        if(e[x].tag != INF) {
            if(L) psdmdy(L, e[x].tag);
            if(R) psdmdy(R, e[x].tag);
            e[x].tag = INF; e[x].rev = 0;
        }
        if(e[x].rev) {
            if(L) psdrev(L);
            if(R) psdrev(R);
            e[x].rev = 0;
        }
    }
    void init() {
        int x;
        e[0] = (Node){0, {0, 0}, 0, 0, 0, 0, 0, -INF, INF, 0};
        e[rt = getid()] = (Node){0, {0, 0}, -INF, 1, -INF, 0, 0, -INF, INF, 0};
        e[x = getid()] = (Node){rt, {0, 0}, -INF, 1, -INF, 0, 0, -INF, INF, 0};
        e[rt].rs = x; upd(rt);
    }
    int idy(int x) {return x == e[e[x].fa].rs;}
    void link(int x, int f, int m) {e[x].fa = f; e[f].ch[m] = x;}
    void rtt(int x) {
        int y = e[x].fa, z = e[y].fa, ix = idy(x), iy = idy(y), s = e[x].ch[ix^1];
        link(s, y, ix); link(y, x, ix^1); link(x, z, iy);
        upd(y), upd(x);
    }
    void splay(int x, int to) {
        while(e[x].fa != to) {
            int y = e[x].fa;
            if(e[y].fa != to) rtt(idy(x) == idy(y) ? y : x);
            rtt(x);
        }
        if(!to) rt = x;
    }
    int kth(int k) {
        k++;
        int x = rt;
        while(1) {
            psd(x);
            if(k <= e[L].s) x = L;
            else if(k <= e[L].s + 1) return x;
            else k -= e[L].s + 1, x = R;
        }
    }
    int build(int l, int r, int a[], int f) {
        if(l > r) return 0;
        int m = (l + r) >> 1;
        int x = getid();
        e[x] = (Node){f, {0, 0}, a[m], 1, a[m], max(a[m], 0), max(a[m], 0), a[m], INF, 0};
        e[x].ls = build(l, m-1, a, x);
        e[x].rs = build(m+1, r, a, x);  
        upd(x);
        return x;
    }
    void ins(int pos, int a[], int n) {
        int lp = kth(pos), rp = kth(pos+1);
        splay(lp, 0); splay(rp, lp);
        int x = build(1, n, a, rp);
        e[rp].ls = x;
        upd(rp), upd(lp);
    }
    void dty(int x) {
        if(!x) return;
        dty(L); dty(R); que.push(x);
    }
    void del(int pos, int tot) {//[pos, pos+tot-1]
        int lp = kth(pos-1), rp = kth(pos+tot);
        splay(lp, 0); splay(rp, lp);
        dty(e[rp].ls); e[rp].ls = 0;
        upd(rp), upd(lp);
    }
    void mdy(int pos, int tot, int c) {
        int lp = kth(pos-1), rp = kth(pos+tot);
        splay(lp, 0); splay(rp, lp);
        psdmdy(e[rp].ls, c);
        upd(rp), upd(lp);
    }
    void rev(int pos, int tot) {
        int lp = kth(pos-1), rp = kth(pos+tot);
        splay(lp, 0); splay(rp, lp);
        psdrev(e[rp].ls);
        upd(rp), upd(lp);
    }
    int sum(int pos, int tot) {
        int lp = kth(pos-1), rp = kth(pos+tot);
        splay(lp, 0); splay(rp, lp);
        return e[e[rp].ls].sum;
    }
    int mxsum() {
        psd(rt);
        return e[rt].mv;
    }
    /*void print(int x) {
        if(!x) return;
        psd(x);
        print(L);
        printf("%d ", e[x].v);
        print(R);
    }*/
    #undef ls
    #undef rs
    #undef L
    #undef R
}
using namespace Splay;
int n, m;
int a[MAXN];
int main() {
    n = read(), m = read();
    init();
    for(int i = 1; i <= n; i++) a[i] = read();
    ins(0, a, n);
    for(int i = 1; i <= m; i++) {
        char s[20];
        scanf("%s", s);
        if(s[2] == 'S') {//ins
            int pos = read(), tot = read();
            for(int i = 1; i <= tot; i++) a[i] = read();
            ins(pos, a, tot);
        } else if(s[2] == 'L') {//del
            int pos = read(), tot = read();
            del(pos, tot);
        } else if(s[2] == 'K') {//mdy
            int pos = read(), tot = read(), c = read();
            mdy(pos, tot, c);
        } else if(s[2] == 'V') {//rev
            int pos = read(), tot = read();
            rev(pos, tot);
        } else if(s[2] == 'T') {//sum
            int pos = read(), tot = read();
            printf("%d\n", sum(pos, tot));
        } else {//mxsum
            printf("%d\n", mxsum());
        }
    }
    return 0;
}
    

无旋Treap(FHQTreap,可持久化Treap,函数式Treap)

分裂、合并

常数大

也可以用来维护序列

<待补>

平衡树总结

通常情况下:

常数:Treap<Splay<FHQTreap

Treap常用于维护有序表

Splay维护有序表效率低,不方便;常用于LCT和维护序列

FHQTreap也可以维护序列,便于实现可持久化,但效率较低

高维问题

原理

维度的组成

对位置有限制,就有位置轴

对权值有限制,就有权值轴

对顺序有要求,就有时间轴

前两者没有本质区别。

时间轴变换

某一轴变为时间轴。方便降维,解决更方便。

降维方法(2种)

  • 离散扫描:将所有询问和数据集合一起按时间排序

  • 在线:可持久化

可以查询历史版本的数据结构。

可以修改的是完全可持久化,否则就是部分可持久化

path copying:新开一遍点,但是没有改动的点保留

fat node:(了解即可)

常用于保持动态插入元素的求解过程,如果可以完全可持久化,则可以保存树形的插入过程

二维数点

  • 裸题

  • 查询一个区间中不小于v的数的个数(位置轴,权值轴)

  • P1972:每次操作询问区间中不同数个数:记录前驱,查询区间(1D),前驱(2D)

三维数点(三维偏序)

分治树维护

维护某一维的基本方法


以下算法时间复杂度都是\(O(nlog^2n)\)

隐式的分治树(必须离线)

cdq分治

通过分治维护时间轴。询问分成两半,算前面一半对后面一半的贡献。(查询时间前缀)

整体二分

用分治维护需要二分的维度,在分治树上二分

  • 静态区间第k小

分治树的组合

树状数组套平衡树

效率一般

空间\(O(nlogn)\)

树状数组套线段树

空间\(O(nlog^2n)\),各个线段树可以同时二分

效率一般

  • 二逼平衡树
线段树套树

支持不可减外层信息

分治套树状数组

离线,空间O(n),效率高,可以在隐式分治树二分

分治套线段树

离线,空间O(n),效率较高,支持较复杂的操作

note:内层如果是权值平衡树,请不要用splay!!!

KDT

维护k维空间矩形信息,\(O(n^{1-1/k})\),空间O(n),支持高维标记,但不能剪枝时常数巨大

根号方法

莫队

P1494

给一个序列a,每次操作给出区间[l,r],求有多少个子区间满足左右端点的数相同

设块大小为l,维护的左端点的移动不超过\(ml+2n\)

右端点的移动不超过\(2n\times ceil(n/l)\),因此取

\(l=ceil(n/\sqrt m)\)得最优复杂度\(O(n\sqrt m)\)

  • 曼哈顿最小生成树问题(很难求,不实用)

回滚莫队

有时候无法简单删除,可以只从左端点开始维护区间,移动右端点并在右端点插入相应元素。移动到右端点时,将左端点未插入,得到答案后再撤销左端点的插入。

可以不必删除,但至少要支持两端插入。

分块,根号平衡

\(O(\sqrt n)-O(1)\)或\(O(1)-O(\sqrt n)\)实现:

  • 序列单点修改,查询区间和

way1:单点修改:块前缀和,块内前缀和

直接查询

way2:直接修改这个点的值,块的和

查询时加一下

  • 序列区间加,查询单点

way1:块打标记,散块暴力维护

自己+块的标记

way2:差分

  • 集合插入,查询k小

way1:?

way2:维护树的出现次数,权值分块

暴力查块(先块外,再块里)

P4396

上一篇:CSP-S 2020炸飞的游记


下一篇:太悲哀了