数据结构模板汇总(新版下)

37.重链剖分

P3384 【模板】重链剖分/树链剖分
如题,已知一棵包含 N N N 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

  • 1 x y z,表示将树从 x x x y y y 结点最短路径上所有节点的值都加上 z z z
  • 2 x y,表示求树从 x x x y y y 结点最短路径上所有节点的值之和。
  • 3 x z,表示将以 x x x 为根节点的子树内所有节点值都加上 z z z
  • 4 x 表示求以 x x x 为根节点的子树内所有节点值之和

O ( l o g 2 n ) O(log^2n) O(log2n)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll INF = 1e18;
const ll N = 1e5 + 10;
vector<vector<int> >e(N);
int n, m, root, p;
int val[N], nval[N], top[N], tag[N << 2], sum[N << 2], sze[N << 2], f[N], siz[N], deep[N], son[N], id[N], cnt;//nval表示树DFS序新编号值
void dfs1(int x, int fa) {
    deep[x] = deep[fa] + 1;
    siz[x] = 1;
    f[x] = fa;
    for (int i : e[x]) {
        if (i == fa) continue;
        dfs1(i, x);
        siz[x] += siz[i];
        if (siz[son[x]] < siz[i]) son[x] = i;
    }
}
void dfs2(int x, int topx) {
    id[x] = ++cnt;//DFS序
    nval[cnt] = val[x];
    top[x] = topx;
    if (!son[x]) return;
    dfs2(son[x], topx);
    for (int i : e[x]) {
        if (i == son[x] || i == f[x]) continue;
        dfs2(i, i);
    }
}
void pushup(int x) {
    sum[x] = (sum[x << 1] + sum[x << 1 | 1]) % p;
}
void pushtag(int x, int k) {
    sum[x] = (sum[x] + k * sze[x]) % p;
    tag[x] = (tag[x] + k) % p;
}
void pushdown(int x) {
    if (tag[x]) {
        pushtag(x << 1, tag[x]);
        pushtag(x << 1 | 1, tag[x]);
        tag[x] = 0;
    }
}
void build(int x, int l, int r) {
    if (l == r) {
        sum[x] = nval[l] % p;
        sze[x] = 1;
        return;
    }
    int mid = l + r >> 1;
    build(x << 1, l, mid);
    build(x << 1 | 1, mid + 1, r);
    sze[x] = sze[x << 1] + sze[x << 1 | 1];
    pushup(x);
}
void treemodify(int x, int l, int r, int ql, int qr, int k) {//线段树修改
    if (ql <= l && qr >= r) {
        pushtag(x, k);
        return;
    }
    pushdown(x);
    int mid = l + r >> 1;
    if (ql <= mid) treemodify(x << 1, l, mid, ql, qr, k);
    if (qr > mid) treemodify(x << 1 | 1, mid + 1, r, ql, qr, k);
    pushup(x);
}
ll treequery(int x, int l, int r, int ql, int qr) {//线段树查询
    if (ql <= l && qr >= r) return sum[x] % p;
    ll res = 0;
    pushdown(x);
    int mid = l + r >> 1;
    if (ql <= mid) res = (res + treequery(x << 1, l, mid, ql, qr)) % p;
    if (qr > mid) res = (res + treequery(x << 1 | 1, mid + 1, r, ql, qr)) % p;
    return res;
}
void modify(int x, int y, int k) {
    while (top[x] != top[y]) {
        if (deep[top[x]] < deep[top[y]]) swap(x, y);
        treemodify(1, 1, n, id[top[x]], id[x], k);//修改整条链
        x = f[top[x]];
    }
    if (deep[x] > deep[y]) swap(x, y);
    treemodify(1, 1, n, id[x], id[y], k);//在同一条链中
}
ll query(int x, int y) {
    ll res = 0;
    while (top[x] != top[y]) {
        if (deep[top[x]] < deep[top[y]]) swap(x, y);
        res = (res + treequery(1, 1, n, id[top[x]], id[x])) % p;//查询整条链
        x = f[top[x]];
    }
    if (deep[x] > deep[y]) swap(x, y);
    res = (res + treequery(1, 1, n, id[x], id[y])) % p;//在同一条链中
    return res;
}
void solve() {
    cin >> n >> m >> root >> p;
    for (int i = 1; i <= n; ++i) cin >> val[i];
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(root, 0);
    dfs2(root, root);//预处理重链
    build(1, 1, n);//建立线段树
    for (int i = 1; i <= m; ++i) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y, k;
            cin >> x >> y >> k;
            modify(x, y, k);
        }
        else if (op == 2) {
            int x, y;
            cin >> x >> y;
            cout << query(x, y) << '\n';
        }
        else if (op == 3) {
            int x, k;
            cin >> x >> k;
            treemodify(1, 1, n, id[x], id[x] + siz[x] - 1, k);//子树DFS序连续
        }
        else {
            int x;
            cin >> x;
            cout << treequery(1, 1, n, id[x], id[x] + siz[x] - 1) << '\n';
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

边权转换为点权

38.长链剖分

P5903 【模板】树上 k 级祖先
给定一棵 n n n 个点的有根树。
q q q 次询问,第 i i i 次询问给定 x i , k i x_i, k_i xi,ki,要求点 x i x_i xi k i k_i ki 级祖先,答案为 a n s i ans_i ansi。特别地, a n s 0 = 0 ans_0 = 0 ans0=0

思路:对树进行长链剖分,记录每个点所在链的顶点和深度。
树上倍增求出每个点的 2 n 2^n 2n 级祖先。
对于每条链,如果其长度为 siz,那么在顶点处记录顶点向上的 siz 个祖先和向下的 siz 个链上的儿子。
对 i∈[1,n] 求出在二进制下的最高位 h i h_i hi
对于每次询问 x 的 k 级祖先,利用倍增数组先将 x 跳到 x 的 2 h k 2^{h_k} 2hk 级祖先,设剩下还有 k′ 级,显然 k′< 2 h k 2^{h_k} 2hk ,因此此时 x 所在的长链长度一定 ≥ 2 h k 2^{h_k} 2hk>k′。
由于长链长度 >k′ ,因此可以先将 x 跳到 x 所在链的顶点,若之后剩下的级数为正,则利用向上的数组求出答案,否则利用向下的数组求出答案。

预处理 O ( n l o g n ) O(nlogn) O(nlogn)
查询 O ( 1 ) O(1) O(1)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 5e5 + 10;
#define ui unsigned int
ui s;
inline ui get(ui x) {
    x ^= x << 13;
    x ^= x >> 17;
    x ^= x << 5;
    return s = x;
}
int deep[N], son[N], top[N], f[N][20], siz[N], bin[N];
vector<vector<int> > e(N), up(N), down(N);//up链顶向上siz个祖先,down向下siz个儿子
void dfs1(int x) {
    deep[x] = deep[f[x][0]] + 1, siz[x] = 1;
    for (int i = 1; (1 << i) <= deep[x]; ++i) f[x][i] = f[f[x][i - 1]][i - 1];
    for (int i : e[x]) {
        dfs1(i);
        siz[x] = max(siz[x], siz[i] + 1);
        if (siz[i] > siz[son[x]]) son[x] = i;
    }
}
void dfs2(int x, int topx) {
    top[x] = topx;
    down[topx].push_back(x);//记录向下儿子
    if (!son[x]) return;
    dfs2(son[x], topx);
    for (int i : e[x]) {
        if (i == son[x]) continue;
        int temp = i;
        up[i].push_back(i);
        for (int j = 1; j <= siz[i]; ++j) {
            up[i].push_back(f[temp][0]);//记录向上祖先
            temp = f[temp][0];
        }
        dfs2(i, i);
    }
}
void solve() {
    int n, q, root;
    cin >> n >> q >> s;
    for (int i = 1; i <= n; ++i) {
        cin >> f[i][0];
        if (!f[i][0]) root = i;
        else e[f[i][0]].push_back(i);
        bin[i] = log2(i);//[1,n]二进制下最高位
    }
    dfs1(root);
    dfs2(root, root);//预处理长链
    int temp = root;
    up[root].push_back(root);
    for (int i = 1; i <= siz[root]; ++i) {
        up[root].push_back(f[temp][0]);
        temp = f[temp][0];
    }
    ll last = 0, ans = 0;
    for (int i = 1; i <= q; ++i) {
        int x = (get(s) ^ last) % n + 1, k = (get(s) ^ last) % deep[x];
        if (k == 0) {
            last = x;
            ans ^= (1ll * i * x);
            continue;
        }
        int goal = deep[x] - k;
        x = top[f[x][bin[k]]];//跳到x的2^bin[k]祖先所在链链顶
        if (deep[x] < goal) x = down[x][goal - deep[x]];//往下找
        else x = up[x][deep[x] - goal];//往上找
        last = x;
        ans ^= (1ll * i * x);
    }
    cout << ans;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

39.启发式合并

P3201 [HNOI2009] 梦幻布丁
n n n 个布丁摆成一行,进行 m m m 次操作。每次将某个颜色的布丁全部变成另一种颜色的,然后再询问当前一共有多少段颜色。
例如,颜色分别为 1 , 2 , 2 , 1 1,2,2,1 1,2,2,1 的四个布丁一共有 3 3 3 段颜色.

  • o p = 1 op = 1 op=1,则后有两个整数 x , y x, y x,y,表示将颜色 x x x 的布丁全部变成颜色 y y y
  • o p = 2 op = 2 op=2,则表示一次询问。

思路:当我们要将个数较多的颜色合并到个数较少的颜色时,我们可以交换两种颜色,即改变映射关系,将颜色少的合并到颜色多的点上。

O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e6 + 10;
int a[N], mp[N], ans = 0;
vector<vector<int> >e(N);
void merge(int& x, int& y) {//引用,改变映射关系
    if (x == y) return;
    if (e[x].size() > e[y].size()) swap(x, y);
    for (int i : e[x]) {
        if (a[i - 1] == y) ans--;
        if (a[i + 1] == y) ans--;
    }
    for (int i : e[x]) a[i] = y;
    e[y].insert(e[y].end(), e[x].begin(), e[x].end());
    e[x].clear();
}
void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        cin >> a[i], mp[a[i]] = a[i];
        e[a[i]].push_back(i);
    }
    for (int i = 1; i <= n; ++i) {
        if (a[i - 1] != a[i]) ans++;
    }
    for (int i = 1; i <= m; ++i) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y;
            cin >> x >> y;
            merge(mp[x], mp[y]);
        }
        else cout << ans << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

40.树上启发式合并

CF600E Lomsat gelral
有一棵 n n n 个结点的以 1 1 1 号结点为根的有根树
每个结点都有一个颜色,颜色是以编号表示的, i i i 号结点的颜色编号为 c i c_i ci
如果一种颜色在以 x x x 为根的子树内出现次数最多,称其在以 x x x 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。
你的任务是对于每一个 i ∈ [ 1 , n ] i\in[1,n] i[1,n],求出以 i i i 为根的子树中,占主导地位的颜色的编号和。

思路:对于一个点 x,首先遍历计算他所有轻儿子的贡献,并且每算完一个儿子就要清除它的所有贡献。接下来计算重儿子的贡献,并保留重儿子的贡献,然后再暴力加入 x 子树中除重儿子以外的所有点的贡献,此时即可得到答案。
随后回溯到 fa[x],如果 x 是 fa[x] 的轻儿子,那么将在 x 这一波计算下来的贡献全部清除,反之则全部保留。

O ( n l o g n ) O(nlogn) O(nlogn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
int son[N], siz[N], color[N], cnt[N];
ll ans[N], maxn = 0, sum = 0;
vector<vector<int> >e(N);
void dfs1(int x, int father) {
    siz[x] = 1;
    for (int i : e[x]) {
        if (i == father) continue;
        dfs1(i, x);
        siz[x] += siz[i];
        if (siz[i] > siz[son[x]]) son[x] = i;
    }
}
void modify(int x, int father, int k, int pson) {
    int c = color[x];
    cnt[c] += k;
    if (cnt[c] > maxn) {
        maxn = cnt[c];
        sum = c;
    }
    else if (cnt[c] == maxn) sum += c;
    for (int i : e[x]) {
        if (i == father || i == pson) continue;//重儿子信息已保留,跳过
        modify(i, x, k, pson);
    }
}
void dfs2(int x, int father, int op) {
    for (int i : e[x]) {
        if (i == father || i == son[x]) continue;//跳过重儿子
        dfs2(i, x, 0);
    }
    if (son[x]) dfs2(son[x], x, 1);//最后遍历重儿子
    modify(x, father, 1, son[x]);
    ans[x] = sum;
    if (op == 0) {//清空轻儿子
        modify(x, father, -1, 0);
        maxn = sum = 0;
    }
}
void solve() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> color[i];
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs1(1, 0);//求重儿子
    dfs2(1, 0, 1);
    for (int i = 1; i <= n; ++i) cout << ans[i] << " ";
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

41.替罪羊树

P3369 【模板】普通平衡树
您需要写一种数据结构(可参考题目标题),来维护一些数,其中需要提供以下操作:

  1. 插入 x x x
  2. 删除 x x x 数(若有多个相同的数,应只删除一个)
  3. 查询 x x x 数的排名(排名定义为比当前数小的数的个数 + 1 +1 +1 )
  4. 查询排名为 x x x 的数
  5. x x x 的前驱(前驱定义为小于 x x x,且最大的数)
  6. x x x 的后继(后继定义为大于 x x x,且最小的数)
#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
const double alpha = 0.7;
struct node {
    int l, r, key, siz, tot, del;//tot实际节点+删除节点,siz实际节点,del=0被删除
}tr[N];
int st[N], order[N];//st用一个栈回收和分配可用节点,order记录拍平后结果
int root = 0, cnt, tot = 0;
void initnode(int x) {//重置节点参数
    tr[x].l = tr[x].r = 0;
    tr[x].siz = tr[x].tot = tr[x].del = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
    tr[x].tot = tr[tr[x].l].tot + tr[tr[x].r].tot + 1;
}
bool notbalance(int x) {//判断子树x是否平衡
    if ((double)tr[x].siz * alpha <= (double)max(tr[tr[x].l].siz, tr[tr[x].r].siz)) return 1;
    else return 0;
}
void inorder(int x) {//中序遍历拍平子树
    if (!x) return;
    inorder(tr[x].l);
    if (tr[x].del) order[++cnt] = x;
    else st[++tot] = x;
    inorder(tr[x].r);
}
void build(int& x, int l, int r) {//重建
    int mid = (l + r) >> 1;
    x = order[mid];
    if (l == r) {
        initnode(x);
        return;
    }
    if (l < mid) build(tr[x].l, l, mid - 1);
    if (l == mid) tr[x].l = 0;
    build(tr[x].r, mid + 1, r);
    pushup(x);
}
void rebuild(int& x) {//重构
    cnt = 0;
    inorder(x);
    if (cnt) build(x, 1, cnt);
    else x = 0;
}
int rnk(int x, int k) {//求k排名
    if (!x) return 0;
    if (k > tr[x].key) return tr[tr[x].l].siz + tr[x].del + rnk(tr[x].r, k);
    else return rnk(tr[x].l, k);
}
int kth(int k) {//排名为k的数
    int x = root;
    while (x) {
        if (tr[x].del && tr[tr[x].l].siz + 1 == k) return tr[x].key;
        else if (tr[tr[x].l].siz >= k) x = tr[x].l;
        else {
            k -= tr[tr[x].l].siz + tr[x].del;
            x = tr[x].r;
        }
    }
    return tr[x].key;
}
void insert(int& x, int k) {//插入数字k
    if (!x) {
        x = st[tot--];
        tr[x].key = k;
        initnode(x);
        return;
    }
    tr[x].siz++;
    tr[x].tot++;
    if (tr[x].key >= k) insert(tr[x].l, k);
    else insert(tr[x].r, k);
    if (notbalance(x)) rebuild(x);
}
void removek(int& x, int k) {//删除排名为k的数
    tr[x].siz--;
    if (tr[x].del && tr[tr[x].l].siz + 1 == k) {
        tr[x].del = 0;
        return;
    }
    if (tr[tr[x].l].siz + tr[x].del >= k) removek(tr[x].l, k);
    else removek(tr[x].r, k - tr[tr[x].l].siz - tr[x].del);
}
void remove(int k) {//删除值为k的数
    removek(root, rnk(root, k) + 1);
    if (tr[root].tot * alpha >= tr[root].siz) rebuild(root);//子树上被删除节点太多,重构
}
void solve() {
    for (int i = N - 1; i >= 1; --i) st[++tot] = i;
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        int op, x;
        cin >> op >> x;
        if (op == 1) insert(root, x);
        else if (op == 2) remove(x);
        else if (op == 3) cout << rnk(root, x) + 1 << '\n';
        else if (op == 4) cout << kth(x) << '\n';
        else if (op == 5) cout << kth(rnk(root, x)) << '\n';
        else cout << kth(rnk(root, x + 1) + 1) << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

42.Treap

P3369 【模板】普通平衡树
O ( l o g n ) O(logn) O(logn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e9;
const ll N = 1e5 + 10;
struct node {
    int l, r, key, val, cnt, siz;//val优先级,满足大根堆;cnt当前值个数
}tr[N];
int root, tot = 0;
int creatnode(int k) {
    tr[++tot].key = k;
    tr[tot].val = rand();
    tr[tot].cnt = tr[tot].siz = 1;
    return tot;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + tr[x].cnt;
}
void build() {
    creatnode(-INF), creatnode(INF);//防止越界
    root = 1;
    tr[1].r = 2;
    pushup(root);
}
void zig(int& x) {//右旋,把x左儿子旋到根
    int p = tr[x].l;
    tr[x].l = tr[p].r, tr[p].r = x, x = p;
    pushup(tr[x].r), pushup(x);
}
void zag(int& x) {//左旋,把x右儿子旋到根
    int p = tr[x].r;
    tr[x].r = tr[p].l, tr[p].l = x, x = p;
    pushup(tr[x].l), pushup(x);
}
void insert(int& x, int k) {
    if (!x) x = creatnode(k);
    else if (k == tr[x].key) tr[x].cnt++;
    else if (k < tr[x].key) {
        insert(tr[x].l, k);
        if (tr[tr[x].l].val > tr[x].val) zig(x);
    }
    else {
        insert(tr[x].r, k);
        if (tr[tr[x].r].val > tr[x].val) zag(x);
    }
    pushup(x);
}
void remove(int& x, int k) {
    if (!x) return;
    if (k == tr[x].key) {
        if (tr[x].cnt > 1) tr[x].cnt--;
        else if (tr[x].l || tr[x].r) {//将x旋转到叶子节点
            if (!tr[x].r || tr[tr[x].l].val > tr[tr[x].r].val) {
                zig(x);
                remove(tr[x].r, k);
            }
            else {
                zag(x);
                remove(tr[x].l, k);
            }
        }
        else x = 0;
    }
    else if (k < tr[x].key) remove(tr[x].l, k);
    else remove(tr[x].r, k);
    pushup(x);
}
int rnk(int x, int k) {//求k的排名
    if (!x) return 0;
    if (k == tr[x].key) return tr[tr[x].l].siz + 1;
    else if (k < tr[x].key) return rnk(tr[x].l, k);
    else return tr[tr[x].l].siz + tr[x].cnt + rnk(tr[x].r, k);
}
int kth(int x, int k) {//排名为k的数
    if (!x) return INF;
    if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
    else if (k <= tr[tr[x].l].siz + tr[x].cnt) return tr[x].key;
    else return kth(tr[x].r, k - tr[tr[x].l].siz - tr[x].cnt);
}
int pre(int x, int k) {
    if (!x) return -INF;
    if (k <= tr[x].key) return pre(tr[x].l, k);
    else return max(tr[x].key, pre(tr[x].r, k));
}
int nxt(int x, int k) {
    if (!x) return INF;
    if (k >= tr[x].key) return nxt(tr[x].r, k);
    else return min(tr[x].key, nxt(tr[x].l, k));
}
void solve() {
    srand(time(0));
    build();
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        int op, x;
        cin >> op >> x;
        if (op == 1) insert(root, x);
        else if (op == 2) remove(root, x);
        else if (op == 3) cout << rnk(root, x) - 1 << '\n';
        else if (op == 4) cout << kth(root, x + 1) << '\n';
        else if (op == 5) cout << pre(root, x) << '\n';
        else cout << nxt(root, x) << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

43.FHQ Treap

普通平衡树

P3369 【模板】普通平衡树
O ( l o g n ) O(logn) O(logn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    int l, r, key, val, siz;
}tr[N];
int root = 0, tot = 0;
void creatnode(int k) {
    tr[++tot].key = k;
    tr[tot].val = rand();
    tr[tot].siz = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void split(int x, int& l, int& r, int k) {//按权值k分裂成l和r两棵树
    if (!x) {
        l = r = 0;
        return;
    }
    if (tr[x].key <= k) {
        l = x;
        split(tr[x].r, tr[x].r, r, k);
    }
    else {
        r = x;
        split(tr[x].l, l, tr[x].l, k);
    }
    pushup(x);
}
int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].val >= tr[y].val) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
void insert(int k) {
    int l, r, p;
    split(root, l, r, k);
    creatnode(k);
    p = merge(l, tot);
    root = merge(p, r);
}
void remove(int k) {
    int l, r, p;
    split(root, l, r, k);
    split(l, l, p, k - 1);
    p = merge(tr[p].l, tr[p].r);
    root = merge(merge(l, p), r);
}
void rnk(int k) {
    int l, r;
    split(root, l, r, k - 1);
    cout << tr[l].siz + 1 << '\n';
    root = merge(l, r);
}
int kth(int x, int k) {
    if (k == tr[tr[x].l].siz + 1) return tr[x].key;
    else if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
    else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void pre(int k) {
    int l, r;
    split(root, l, r, k - 1);
    cout << kth(l, tr[l].siz) << '\n';
    root = merge(l, r);
}
void nxt(int k) {
    int l, r;
    split(root, l, r, k);
    cout << kth(r, 1) << '\n';
    root = merge(l, r);
}
void solve() {
    srand(time(0));
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        int op, x;
        cin >> op >> x;
        if (op == 1) insert(x);
        else if (op == 2) remove(x);
        else if (op == 3) rnk(x);
        else if (op == 4) cout << kth(root, x) << '\n';
        else if (op == 5) pre(x);
        else nxt(x);
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}
}

排名分裂

P4008 [NOI2003] 文本编辑器

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 3e6 + 10;
struct node {
    int l, r, val, siz;
    char key;
}tr[N];
int root = 0, tot = 0, pos = 0;
void creatnode(char k) {
    tr[++tot].key = k;
    tr[tot].val = rand();
    tr[tot].siz = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void split(int x, int& l, int& r, int k) {//按排名k分裂成l和r两棵树
    if (!x) {
        l = r = 0;
        return;
    }
    if (tr[tr[x].l].siz + 1 <= k) {
        l = x;
        split(tr[x].r, tr[x].r, r, k - tr[tr[x].l].siz - 1);
    }
    else {
        r = x;
        split(tr[x].l, l, tr[x].l, k);
    }
    pushup(x);
}
int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].val >= tr[y].val) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
void insert(int len) {
    int l, r;
    split(root, l, r, pos);
    for (int i = 1; i <= len; ++i) {
        char ch = getchar();
        while (ch < 32 || ch>126) ch = getchar();
        creatnode(ch);
        l = merge(l, tot);
    }
    root = merge(l, r);
}
void remove(int len) {
    int l, r, p;
    split(root, l, r, pos + len);
    split(l, l, p, pos);
    root = merge(l, r);
}
void inorder(int x) {
    if (!x) return;
    inorder(tr[x].l);
    cout << tr[x].key;
    inorder(tr[x].r);
}
void get(int len) {
    int l, r, p;
    split(root, l, r, pos + len);
    split(l, l, p, pos);
    inorder(p);
    root = merge(merge(l, p), r);
}
void solve() {
    srand(time(0));
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        string op;
        int len;
        cin >> op;
        if (op[0] == 'M') cin >> pos;
        else if (op[0] == 'I') {
            cin >> len;
            insert(len);
        }
        else if (op[0] == 'D') {
            cin >> len;
            remove(len);
        }
        else if (op[0] == 'G') {
            cin >> len;
            get(len);
            cout << '\n';
        }
        else if (op[0] == 'P') pos--;
        else pos++;
    }
}
int main() {
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

区间翻转(文艺平衡树)

P3391 【模板】文艺平衡树
您需要写一种数据结构(可参考题目标题),来维护一个有序数列。
其中需要提供以下操作:翻转一个区间,例如原有序序列是 5   4   3   2   1 5\ 4\ 3\ 2\ 1 5 4 3 2 1,翻转区间是 [ 2 , 4 ] [2,4] [2,4] 的话,结果是 5   2   3   4   1 5\ 2\ 3\ 4\ 1 5 2 3 4 1

思路:Lazy-Tag
对一个区间做翻转操作,可以以区间中任意数为轴,左右交换,然后对左、右两部分继续递归这个操作,直到结束,最后得到翻转的结果。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    int l, r, key, val, siz, tag;//tag=1区间需要翻转
}tr[N];
int root = 0, tot = 0;
void creatnode(int k) {
    tr[++tot].key = k;
    tr[tot].val = rand();
    tr[tot].siz = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void pushdown(int x) {
    if (tr[x].tag) {
        swap(tr[x].l, tr[x].r);
        tr[tr[x].l].tag ^= 1;
        tr[tr[x].r].tag ^= 1;
        tr[x].tag = 0;
    }
}
void split(int x, int& l, int& r, int k) {//排名分裂
    if (!x) {
        l = r = 0;
        return;
    }
    pushdown(x);
    if (tr[tr[x].l].siz + 1 <= k) {
        l = x;
        split(tr[x].r, tr[x].r, r, k - tr[tr[x].l].siz - 1);
    }
    else {
        r = x;
        split(tr[x].l, l, tr[x].l, k);
    }
    pushup(x);
}
int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].val >= tr[y].val) {
        pushdown(x);
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;

    }
    else {
        pushdown(y);
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
void inorder(int x) {
    if (!x) return;
    pushdown(x);
    inorder(tr[x].l);
    cout << tr[x].key << " ";
    inorder(tr[x].r);
}
void solve() {
    srand(time(0));
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) {
        creatnode(i);
        root = merge(root, tot);
    }
    for (int i = 1; i <= m; ++i) {
        int l, r, p, x, y;
        cin >> x >> y;
        split(root, l, r, y);
        split(l, l, p, x - 1);
        tr[p].tag ^= 1;
        root = merge(merge(l, p), r);
    }
    inorder(root);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

可持久化平衡树

P3835 【模板】可持久化平衡树
您需要写一种数据结构(可参考题目标题),来维护一个可重整数集合,其中需要提供以下操作( 对于各个以往的历史版本 ):

  1. 插入 x x x
  2. 删除 x x x(若有多个相同的数,应只删除一个,如果没有请忽略该操作
  3. 查询 x x x 的排名(排名定义为比当前数小的数的个数 + 1 +1 +1
  4. 查询排名为 x x x 的数
  5. x x x 的前驱(前驱定义为小于 x x x,且最大的数,如不存在输出 − 2 31 + 1 -2^{31}+1 231+1
  6. x x x 的后继(后继定义为大于 x x x,且最小的数,如不存在输出 2 31 − 1 2^{31}-1 2311

和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本。(操作3, 4, 5, 6即保持原版本无变化)
每个版本的编号即为操作的序号(版本0即为初始状态,空树)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 2 << 30 - 1;
const ll N = 5e5 + 10;
struct node {
    int l, r, key, val, siz;
}tr[N << 7];
int root[N], tot = 0, idx = 0;
void creatnode(int k) {
    tr[++tot].key = k;
    tr[tot].val = rand();
    tr[tot].siz = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
int clone(int x) {//复制树,只需要复制树根
    ++tot;
    tr[tot] = tr[x];
    return tot;
}
void split(int x, int& l, int& r, int k) {
    if (!x) {
        l = r = 0;
        return;
    }
    if (tr[x].key <= k) {
        l = clone(x);//副本
        split(tr[l].r, tr[l].r, r, k);
        pushup(l);
    }
    else {
        r = clone(x);
        split(tr[r].l, l, tr[r].l, k);
        pushup(r);
    }
}
int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].val >= tr[y].val) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
int kth(int x, int k) {
    if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
    else if (k == tr[tr[x].l].siz + 1) return tr[x].key;
    else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void solve() {
    srand(time(0));
    int n;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        int v, op, x, l, r, p;
        cin >> v >> op >> x;
        if (op == 1) {
            split(root[v], l, r, x);
            creatnode(x);
            root[++idx] = merge(merge(l, tot), r);
        }
        else if (op == 2) {
            split(root[v], l, r, x);
            split(l, l, p, x - 1);
            p = merge(tr[p].l, tr[p].r);
            root[++idx] = merge(merge(l, p), r);
        }
        else if (op == 3) {
            split(root[v], l, r, x - 1);
            cout << tr[l].siz + 1 << '\n';
            root[++idx] = merge(l, r);
        }
        else if (op == 4) {
            cout << kth(root[v], x) << '\n';
            root[++idx] = root[v];
        }
        else if (op == 5) {
            split(root[v], l, r, x - 1);
            if (!l) cout << -INF << '\n';
            else cout << kth(l, tr[l].siz) << '\n';
            root[++idx] = merge(l, r);
        }
        else {
            split(root[v], l, r, x);
            if (!r) cout << INF << '\n';
            else cout << kth(r, 1) << '\n';
            root[++idx] = merge(l, r);
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

可持久化文艺平衡树

P5055 【模板】可持久化文艺平衡树
您需要写一种数据结构,来维护一个序列,其中需要提供以下操作(对于各个以往的历史版本):

  1. 在第 p p p 个数后插入数 x x x
  2. 删除第 p p p 个数。
  3. 翻转区间 [ l , r ] [l,r] [l,r],例如原序列是 { 5 , 4 , 3 , 2 , 1 } \{5,4,3,2,1\} {5,4,3,2,1},翻转区间 [ 2 , 4 ] [2,4] [2,4] 后,结果是 { 5 , 2 , 3 , 4 , 1 } \{5,2,3,4,1\} {5,2,3,4,1}
  4. 查询区间 [ l , r ] [l,r] [l,r] 中所有数的和。

和原本平衡树不同的一点是,每一次的任何操作都是基于某一个历史版本,同时生成一个新的版本(操作 4 4 4 即保持原版本无变化),新版本即编号为此次操作的序号。
本题强制在线。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e5 + 10;
struct node {
    ll l, r, key, val, sum, siz, tag;
}tr[N << 7];
ll root[N], tot = 0, idx = 0;
void creatnode(ll k) {
    ++tot;
    tr[tot].key = tr[tot].sum = k;
    tr[tot].val = rand();
    tr[tot].siz = 1;
}
ll clone(ll x) {//复制树,只需要复制树根
    ++tot;
    tr[tot] = tr[x];
    return tot;
}
void pushup(ll x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
    tr[x].sum = tr[tr[x].l].sum + tr[tr[x].r].sum + tr[x].key;
}
void pushdown(ll x) {
    if (tr[x].tag) {
        if (tr[x].l) tr[x].l = clone(tr[x].l);//副本
        if (tr[x].r) tr[x].r = clone(tr[x].r);
        swap(tr[x].l, tr[x].r);
        tr[tr[x].l].tag ^= 1, tr[tr[x].r].tag ^= 1;
        tr[x].tag = 0;
    }
}
void split(ll x, ll& l, ll& r, ll k) {
    if (!x) {
        l = r = 0;
        return;
    }
    pushdown(x);
    if (tr[tr[x].l].siz + 1 <= k) {
        l = clone(x);//副本
        split(tr[l].r, tr[l].r, r, k - tr[tr[x].l].siz - 1);
        pushup(l);
    }
    else {
        r = clone(x);
        split(tr[r].l, l, tr[r].l, k);
        pushup(r);
    }
}
ll merge(ll x, ll y) {
    if (!x || !y) return x + y;
    if (tr[x].val >= tr[y].val) {
        pushdown(x);
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        pushdown(y);
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
void solve() {
    srand(time(0));
    ll n, last = 0;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        ll v, op;
        cin >> v >> op;
        if (op == 1) {
            ll p, x, L, R;
            cin >> p >> x;
            p ^= last, x ^= last;
            split(root[v], L, R, p);
            creatnode(x);
            root[++idx] = merge(merge(L, tot), R);
        }
        else if (op == 2) {
            ll p, L, R, tmp;
            cin >> p;
            p ^= last;
            split(root[v], L, R, p);
            split(L, L, tmp, p - 1);
            root[++idx] = merge(L, R);
        }
        else if (op == 3) {
            ll l, r, L, R, tmp;
            cin >> l >> r;
            l ^= last, r ^= last;
            split(root[v], L, R, r);
            split(L, L, tmp, l - 1);
            tr[tmp].tag ^= 1;
            root[++idx] = merge(merge(L, tmp), R);
        }
        else {
            ll l, r, L, R, tmp;
            cin >> l >> r;
            l ^= last, r ^= last;
            split(root[v], L, R, r);
            split(L, L, tmp, l - 1);
            last = tr[tmp].sum;
            cout << last << '\n';
            root[++idx] = merge(merge(L, tmp), R);
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

44.Splay

排名分裂

P4008 [NOI2003] 文本编辑器

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 4e6 + 10;
struct node {
    char key;
    int l, r, fa, siz;
}tr[N];
int root = 1, tot = 2;
char s[N];
void init() {//防止越界
    tr[1].siz = 2, tr[1].l = 2;
    tr[2].siz = 1, tr[2].fa = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void rotate(int x) {
    int y = tr[x].fa, z = tr[y].fa;
    int k = (x == tr[y].r);
    if (k == 1) {//x是右儿子,左旋
        tr[y].r = tr[x].l;
        tr[tr[x].l].fa = y;
        tr[x].l = y;
    }
    else {//x是左儿子,右旋
        tr[y].l = tr[x].r;
        tr[tr[x].r].fa = y;
        tr[x].r = y;
    }
    tr[y].fa = x;
    tr[x].fa = z;
    if (z) {//更新祖父的儿子
        if (tr[z].l == y) tr[z].l = x;
        else tr[z].r = x;
    }
    pushup(y), pushup(x);
}
void splay(int x, int k) {//把x旋转为k的儿子
    while (tr[x].fa != k) {
        int y = tr[x].fa, z = tr[y].fa;
        if (z != k) {
            if ((x == tr[y].r) == (y == tr[z].r)) rotate(y);//一字型
            else rotate(x);//之字型
        }
        rotate(x);
    }
    if (k == 0) root = x;
}
int build(int l, int r, int f) {
    if (l > r) return 0;
    int mid = l + r >> 1;
    int cur = ++tot;
    tr[cur].fa = f;
    tr[cur].key = s[mid];
    tr[cur].l = build(l, mid - 1, cur);
    tr[cur].r = build(mid + 1, r, cur);
    pushup(cur);
    return cur;
}
int kth(int x, int k) {
    if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
    else if (k == tr[tr[x].l].siz + 1) return x;
    else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void insert(int pos, int len) {
    int l = kth(root, pos), r = kth(root, pos + 1);
    splay(l, 0), splay(r, l);//把l旋至根,r旋至l的儿子
    tr[r].l = build(1, len, r);//建一棵树挂到左儿子
    pushup(r), pushup(l);
}
void remove(int x, int len) {
    int l = kth(root, x), r = kth(root, x + len + 1);
    splay(l, 0), splay(r, l);
    tr[r].l = 0;//剪断左子树,等于删除
    pushup(r), pushup(l);
}
void inorder(int x) {
    if (!x) return;
    inorder(tr[x].l);
    cout << tr[x].key;
    inorder(tr[x].r);
}
void output(int pos, int len) {
    int l = kth(root, pos), r = kth(root, pos + len + 1);
    splay(l, 0), splay(r, l);
    inorder(tr[r].l);
    cout << '\n';
}
void solve() {
    int n, len, pos = 1;
    cin >> n;
    init();
    for (int i = 1; i <= n; ++i) {
        string op;
        cin >> op;
        if (op[0] == 'M') cin >> pos, pos++;
        else if (op[0] == 'I') {
            cin >> len;
            for (int i = 1; i <= len; ++i) {
                char ch = getchar();
                while (ch < 32 || ch>126) ch = getchar();
                s[i] = ch;
            }
            insert(pos, len);
        }
        else if (op[0] == 'D') {
            cin >> len;
            remove(pos, len);
        }
        else if (op[0] == 'G') {
            cin >> len;
            output(pos, len);
        }
        else if (op[0] == 'P') pos--;
        else pos++;
    }
}
int main() {
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

区间翻转(文艺平衡树)

P3391 【模板】文艺平衡树

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    int l, r, fa, key, siz, tag;
}tr[N];
int root, tot = 0, n, m;
void creatnode(int k, int p) {
    tr[++tot].key = k;
    tr[tot].fa = p;
    tr[tot].siz = 1;
}
void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
void pushdown(int x) {
    if (tr[x].tag) {
        swap(tr[x].l, tr[x].r);
        tr[tr[x].l].tag ^= 1;
        tr[tr[x].r].tag ^= 1;
        tr[x].tag = 0;
    }
}
void rotate(int x) {
    int y = tr[x].fa, z = tr[y].fa;
    int k = (tr[y].r == x);
    if (k == 1) {
        tr[y].r = tr[x].l;
        tr[tr[x].l].fa = y;
        tr[x].l = y;
    }
    else {
        tr[y].l = tr[x].r;
        tr[tr[x].r].fa = y;
        tr[x].r = y;
    }
    tr[y].fa = x;
    tr[x].fa = z;
    if (tr[z].l == y) tr[z].l = x;
    else tr[z].r = x;
    pushup(y), pushup(x);
}
void splay(int x, int k) {
    while (tr[x].fa != k) {
        int y = tr[x].fa, z = tr[y].fa;
        if (z != k) {
            if ((tr[y].r == x) == (tr[z].r == y)) rotate(y);
            else rotate(x);
        }
        rotate(x);
    }
    if (k == 0) root = x;
}
void insert(int k) {
    int x = root, p = 0;
    while (x) {
        p = x;
        if (tr[x].key < k) x = tr[x].r;
        else x = tr[x].l;
    }
    creatnode(k, p);
    if (p) {
        if (k > tr[p].key) tr[p].r = tot;
        else tr[p].l = tot;
    }
    splay(tot, 0);
}
int kth(int x, int k) {
    pushdown(x);
    if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
    else if (k == tr[tr[x].l].siz + 1) return x;
    else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
void inorder(int x) {
    if (!x) return;
    pushdown(x);
    inorder(tr[x].l);
    if (tr[x].key >= 1 && tr[x].key <= n) cout << tr[x].key << " ";
    inorder(tr[x].r);
}
void solve() {
    cin >> n >> m;
    for (int i = 0; i <= n + 1; ++i) insert(i);
    for (int i = 1; i <= m; ++i) {
        int l, r;
        cin >> l >> r;
        l = kth(root, l), r = kth(root, r + 2);
        splay(l, 0), splay(r, l);
        tr[tr[r].l].tag ^= 1;
    }
    inorder(root);
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

分裂、合并、删除节点操作

45.笛卡尔树

P5854 【模板】笛卡尔树
给定一个 1 ∼ n 1 \sim n 1n 的排列 p p p,构建其笛卡尔树。
即构建一棵二叉树,满足:

  1. 每个节点的编号满足二叉搜索树的性质。
  2. 节点 i i i 的权值为 p i p_i pi,每个节点的权值满足小根堆的性质。

建树 O ( n ) O(n) O(n)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e9;
const ll N = 1e7 + 10;
struct node {
    int l, r, val, fa;
}tr[N];
int read() {
    int res = 0;
    char ch = getchar();
    while (ch < '0' || ch>'9') ch = getchar();
    while (ch >= '0' && ch <= '9') { res = (res << 3) + (res << 1) + (ch ^ 48); ch = getchar(); }
    return res;
}
void build(int n) {
    for (int i = 1; i <= n; ++i) {
        int p = i - 1;
        while (tr[i].val < tr[p].val) p = tr[p].fa;//沿最右链一直找
        tr[i].l = tr[p].r;
        tr[tr[p].r].fa = i;
        tr[p].r = i;
        tr[i].fa = p;
    }
}
void solve() {
    ll n, ans1 = 0, ans2 = 0;
    n = read();
    for (int i = 1; i <= n; ++i) tr[i].val = read();
    tr[0].val = -INF;
    build(n);
    for (int i = 1; i <= n; ++i) {
        ans1 ^= 1ll * i * (tr[i].l + 1);
        ans2 ^= 1ll * i * (tr[i].r + 1);
    }
    cout << ans1 << " " << ans2;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

RMQ为区间端点LCA的值

46.线段树套平衡树

P3380 【模板】二逼平衡树(树套树)
您需要写一种数据结构(可参考题目标题),来维护一个有序数列,其中需要提供以下操作:

  1. 查询 k k k 在区间内的排名
  2. 查询区间内排名为 k k k 的值
  3. 修改某一位置上的数值
  4. 查询 k k k 在区间内的前驱(前驱定义为严格小于 x x x,且最大的数,若不存在输出 -2147483647
  5. 查询 k k k 在区间内的后继(后继定义为严格大于 x x x,且最小的数,若不存在输出 2147483647

思路:用线段树划分序列,每个节点开一棵平衡树,用来维护该段区间元素信息

排名为k的值 O ( l o g 3 n ) O(log^3n) O(log3n)
其余 O ( l o g 2 n ) O(log^2n) O(log2n)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 2147483647;
const ll N = 2e6 + 10;
struct tree {
    int l, r, siz, key, val;
}tr[N];
int a[N], root[N], tot = 0;
inline int read() {
    int res = 0;
    char ch = getchar();
    while (ch < '0' || ch>'9') ch = getchar();
    while (ch >= '0' && ch <= '9') { res = (res << 3) + (res << 1) + (ch ^ 48); ch = getchar(); }
    return res;
}
inline void pushup(int x) {
    tr[x].siz = tr[tr[x].l].siz + tr[tr[x].r].siz + 1;
}
inline void creatnode(int k) {
    tr[++tot].key = k;
    tr[tot].val = rand();
    tr[tot].siz = 1;
}
inline void split(int x, int& l, int& r, int k) {
    if (!x) {
        l = r = 0;
        return;
    }
    if (tr[x].key <= k) {
        l = x;
        split(tr[x].r, tr[x].r, r, k);
    }
    else {
        r = x;
        split(tr[x].l, l, tr[x].l, k);
    }
    pushup(x);
}
inline int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].val >= tr[y].val) {
        tr[x].r = merge(tr[x].r, y);
        pushup(x);
        return x;
    }
    else {
        tr[y].l = merge(x, tr[y].l);
        pushup(y);
        return y;
    }
}
inline void insert(int& rt, int k) {
    int l, r;
    split(rt, l, r, k);
    creatnode(k);
    rt = merge(merge(l, tot), r);
}
inline void remove(int& rt, int k) {
    int l, r, p;
    split(rt, l, r, k);
    split(l, l, p, k - 1);
    p = merge(tr[p].l, tr[p].r);
    rt = merge(merge(l, p), r);
}
inline int rnk(int& rt, int k) {//查询小于k数量,即k的排名-1,
    int u = rt, res = 0;
    while (u) {
        if (tr[u].key < k) res += tr[tr[u].l].siz + 1, u = tr[u].r;
        else u = tr[u].l;
    }
    return res;
}
inline int kth(int x, int k) {//查询排名为k的值
    if (k == tr[tr[x].l].siz + 1) return tr[x].key;
    else if (k <= tr[tr[x].l].siz) return kth(tr[x].l, k);
    else return kth(tr[x].r, k - tr[tr[x].l].siz - 1);
}
inline int pre(int& rt, int k) {
    int l, r;
    split(rt, l, r, k - 1);
    int res = kth(l, tr[l].siz);
    rt = merge(l, r);
    return res;
}
inline int nxt(int& rt, int k) {
    int l, r;
    split(rt, l, r, k);
    int res = kth(r, 1);
    rt = merge(l, r);
    return res;
}
inline void build(int x, int l, int r) {
    insert(root[x], -INF), insert(root[x], INF);
    for (register int i = l; i <= r; ++i) insert(root[x], a[i]);
    int mid = l + r >> 1;
    if (l == r) return;
    build(x << 1, l, mid);
    build(x << 1 | 1, mid + 1, r);
}
inline void modify(int x, int l, int r, int p, int k) {//线段树修改
    remove(root[x], a[p]);
    insert(root[x], k);
    if (l == r) return;
    int mid = l + r >> 1;
    if (p <= mid) modify(x << 1, l, mid, p, k);
    else modify(x << 1 | 1, mid + 1, r, p, k);
}
inline int query(int x, int l, int r, int ql, int qr, int k) {//线段树查询区间k排名
    if (ql <= l && qr >= r) return rnk(root[x], k) - 1;
    int res = 0;
    int mid = l + r >> 1;
    if (ql <= mid) res += query(x << 1, l, mid, ql, qr, k);
    if (qr > mid) res += query(x << 1 | 1, mid + 1, r, ql, qr, k);
    return res;
}
inline int querypre(int x, int l, int r, int ql, int qr, int k) {//线段树查询区间前驱
    if (ql <= l && qr >= r) return pre(root[x], k);
    int res = -INF;
    int mid = l + r >> 1;
    if (ql <= mid) res = max(res, querypre(x << 1, l, mid, ql, qr, k));
    if (qr > mid) res = max(res, querypre(x << 1 | 1, mid + 1, r, ql, qr, k));
    return res;
}
inline int querynxt(int x, int l, int r, int ql, int qr, int k) {//线段树查询区间后继
    if (ql <= l && qr >= r) return nxt(root[x], k);
    int res = INF;
    int mid = l + r >> 1;
    if (ql <= mid) res = min(res, querynxt(x << 1, l, mid, ql, qr, k));
    if (qr > mid) res = min(res, querynxt(x << 1 | 1, mid + 1, r, ql, qr, k));
    return res;
}
inline void solve() {
    srand(time(0));
    int n, m;
    n = read(), m = read();
    for (register int i = 1; i <= n; ++i) a[i] = read();
    build(1, 1, n);
    for (int i = 1; i <= m; ++i) {
        int op, k;
        op = read();
        if (op == 1) {
            int l, r;
            l = read(), r = read(), k = read();
            cout << query(1, 1, n, l, r, k) + 1 << '\n';
        }
        else if (op == 2) {
            int l, r;
            l = read(), r = read(), k = read();
            int L = 1, R = 1e8;
            while (L <= R) {//二分
                int mid = L + R >> 1;
                if (query(1, 1, n, l, r, mid) + 1 <= k) L = mid + 1;
                else R = mid - 1;
            }
            cout << R << '\n';
        }
        else if (op == 3) {
            int pos;
            pos = read(), k = read();
            modify(1, 1, n, pos, k);
            a[pos] = k;
        }
        else if (op == 4) {
            int l, r;
            l = read(), r = read(), k = read();
            cout << querypre(1, 1, n, l, r, k) << '\n';
        }
        else {
            int l, r;
            l = read(), r = read(), k = read();
            cout << querynxt(1, 1, n, l, r, k) << '\n';
        }
    }
}
int main() {
    //ios::sync_with_stdio(false);
    //cin.tie(0);
    //cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

47.树状数组套线段树

P3157 [CQOI2011] 动态逆序对
对于序列 a a a,它的逆序对数定义为集合 { ( i , j ) ∣ i < j ∧ a i > a j } \{(i,j)| i<j \wedge a_i > a_j \} {(i,j)i<jai>aj}中的元素个数。
现在给出 1 ∼ n 1\sim n 1n 的一个排列,按照某种顺序依次删除 m m m 个元素,你的任务是在每次删除一个元素之前统计整个序列的逆序对数。 1 ≤ n ≤ 1 0 5 1\le n \le 10^5 1n105 1 ≤ m ≤ 50000 1\le m \le 50000 1m50000

在线
时间 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)
空间 O ( n log ⁡ 2 n ) O(n\log^2 n) O(nlog2n)
动态逆序对还有CDQ分治离线做法
博客

48.KD树

对k维空间进行划分的二叉树,二维时按x和y中值进行交替二分和建树

最近邻点

hdu2966 In case of failure
为了帮助客户处理出现故障的自动取款机,平面银行董事会决定在每台自动取款机上都贴上“本行对故障深表歉意”的标签。同样的标签会温和地要求顾客冷静地走向最近的机器(希望这样可以。
工作好)。
为了做到这一点,已经准备好了所有n个atm机的二维位置列表,您的任务是为每个atm机找到一个相对于欧几里得距离最近的位置。

O ( l o g n ) ∼ O ( n ) O(logn)\sim O(n) O(logn)O(n)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
const int k = 2;
struct Point {
    ll dim[k];
}p[N], tr[N];//tr存储二叉树
ll ans, cur;
bool cmp(Point a, Point b) {
    return a.dim[cur] < b.dim[cur];
}
ll sq(ll x) {
    return x * x;
}
ll dis(Point a, Point b) {
    return sq(a.dim[0] - b.dim[0]) + sq(a.dim[1] - b.dim[1]);
}
void build(int l, int r, int dep) {
    if (l >= r) return;
    int mid = l + r >> 1;
    cur = dep;
    nth_element(tr + l, tr + mid, tr + r, cmp);//将中值元素放在tr数组中适当位置,其他元素按照中值大小划分。
    build(l, mid, (dep + 1) % k);
    build(mid + 1, r, (dep + 1) % k);
}
void query(int l, int r, int dep, Point a) {//查找p的最近点
    if (l >= r) return;
    int mid = l + r >> 1;
    ll d = dis(tr[mid], a);
    if (!ans || d && ans > d) ans = d;//特判tr[mid]与p重合
    if (a.dim[dep] > tr[mid].dim[dep]) {//p该维大于子树的根,查右子树
        query(mid + 1, r, (dep + 1) % k, a);
        if (ans > sq(a.dim[dep] - tr[mid].dim[dep]))//若以ans为半径圆与左子树相交,那么左子树也要查
            query(l, mid, (dep + 1) % k, a);
    }
    else {
        query(l, mid, (dep + 1) % k, a);
        if (ans > sq(a.dim[dep] - tr[mid].dim[dep]))
            query(mid + 1, r, (dep + 1) % k, a);
    }
}
void solve() {
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        cin >> p[i].dim[0] >> p[i].dim[1];
        tr[i] = p[i];
    }
    build(0, n, 0);
    for (int i = 0; i < n; ++i) {
        ans = 0;
        query(0, n, 0, p[i]);
        cout << ans << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t;
    //_t = 1;
    cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

区间查询

P4148 简单题
你有一个 N × N N \times N N×N的棋盘,每个格子内有一个整数,初始时的时候全部为 0 0 0,现在需要维护两种操作:

  • 1 x y A 1 ≤ x , y ≤ N 1\le x,y\le N 1x,yN A A A 是正整数。将格子x,y里的数字加上 A A A
  • 2 x1 y1 x2 y2 1 ≤ x 1 ≤ x 2 ≤ N 1 \le x_1 \le x_2 \le N 1x1x2N 1 ≤ y 1 ≤ y 2 ≤ N 1 \le y_1\le y_2 \le N 1y1y2N。输出 x 1 , y 1 , x 2 , y 2 x_1, y_1, x_2, y_2 x1,y1,x2,y2 这个矩形内的数字和
  • 3 无 终止程序

思路:KD树把二维空间分为一个个小矩形,转换成二叉树,每个小矩形对应一棵子树。只要统计这个矩形范围包含了哪些小矩形,计算这些小矩形的数字和即可。

O ( l o g n ) O(logn) O(logn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e5 + 10;
const double alpha = 0.75;
struct point {
    int dim[2], k;
    point() {}
    point(int x, int y, int key) {
        dim[0] = x;
        dim[1] = y;
        k = key;
    }
}order[N];
struct node {
    int l, r, sum, siz, mn[2], mx[2];
    point p;
}tr[N];
int root, tot, cur, top, st[N], cnt;
bool cmp(point a, point b) {
    return a.dim[cur] < b.dim[cur];
}
void pushup(int u) {
    for (int i = 0; i < 2; ++i) {
        tr[u].mn[i] = tr[u].mx[i] = tr[u].p.dim[i];
        if (tr[u].l) {
            tr[u].mn[i] = min(tr[u].mn[i], tr[tr[u].l].mn[i]);
            tr[u].mx[i] = max(tr[u].mx[i], tr[tr[u].l].mx[i]);
        }
        if (tr[u].r) {
            tr[u].mn[i] = min(tr[u].mn[i], tr[tr[u].r].mn[i]);
            tr[u].mx[i] = max(tr[u].mx[i], tr[tr[u].r].mx[i]);
        }
    }
    tr[u].sum = tr[tr[u].l].sum + tr[tr[u].r].sum + tr[u].p.k;
    tr[u].siz = tr[tr[u].l].siz + tr[tr[u].r].siz + 1;
}
void slap(int u) {//替罪羊树拍平
    if (!u) return;
    slap(tr[u].l);
    order[++cnt] = tr[u].p;
    st[++top] = u;
    slap(tr[u].r);
}
int build(int l, int r, int d) {//替罪羊树重建
    if (l > r) return 0;
    int u;
    if (top) u = st[top--];
    else u = ++tot;
    int mid = l + r >> 1;
    cur = d;
    nth_element(order + l, order + mid, order + r + 1, cmp);
    tr[u].p = order[mid];
    tr[u].l = build(l, mid - 1, d ^ 1);
    tr[u].r = build(mid + 1, r, d ^ 1);
    pushup(u);
    return u;
}
bool notbalance(int u) {//判断子树是否平衡
    if (tr[tr[u].l].siz > alpha * tr[u].siz || tr[tr[u].r].siz > alpha * tr[u].siz) return 1;
    else return 0;
}
void insert(int& u, point x, int d) {
    if (!u) {
        if (top) u = st[top--];
        else u = ++tot;
        tr[u].l = tr[u].r = 0;
        tr[u].p = x;
        pushup(u);
        return;
    }
    if (x.dim[d] <= tr[u].p.dim[d]) insert(tr[u].l, x, d ^ 1);
    else insert(tr[u].r, x, d ^ 1);
    pushup(u);
    if (notbalance(u)) {
        cnt = 0;
        slap(u);
        u = build(1, tr[u].siz, d);
    }
}
int query(int u, int x1, int y1, int x2, int y2) {
    if (!u) return 0;
    int xx1 = tr[u].mn[0], yy1 = tr[u].mn[1], xx2 = tr[u].mx[0], yy2 = tr[u].mx[1];
    if (x1 <= xx1 && x2 >= xx2 && y1 <= yy1 && y2 >= yy2) return tr[u].sum;//子树表示矩形完全在查询范围内
    if (x1 > xx2 || x2<xx1 || y1>yy2 || y2 < yy1) return 0;//子树表示矩形完全在查询范围外
    int ans = 0;
    xx1 = tr[u].p.dim[0], yy1 = tr[u].p.dim[1], xx2 = tr[u].p.dim[0], yy2 = tr[u].p.dim[1];
    if (x1 <= xx1 && x2 >= xx2 && y1 <= yy1 && y2 >= yy2) ans += tr[u].p.k;//根在查询矩阵内
    ans += query(tr[u].l, x1, y1, x2, y2) + query(tr[u].r, x1, y1, x2, y2);//递归左右子树
    return ans;
}
void solve() {
    int n, ans = 0;
    cin >> n;
    while (1) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y, k;
            cin >> x >> y >> k;
            x ^= ans, y ^= ans, k ^= ans;
            insert(root, point(x, y, k), 0);
        }
        else if (op == 2) {
            int x1, y1, x2, y2;
            cin >> x1 >> y1 >> x2 >> y2;
            x1 ^= ans, y1 ^= ans, x2 ^= ans, y2 ^= ans;
            ans = query(root, x1, y1, x2, y2);
            cout << ans << '\n';
        }
        else break;
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

K近邻

P2093 [国家集训队] JZPFAR
平面上有 n n n 个点。现在有 m m m 次询问,每次给定一个点 ( p x , p y ) (px, py) (px,py) 和一个整数 k k k,输出 n n n 个点中离 ( p x , p y ) (px, py) (px,py) 的距离第 k k k 大的点的标号。如果有两个(或多个)点距离 ( p x , p y ) (px, py) (px,py) 相同,那么认为标号较小的点距离较大。

思路:用优先队列动态维护小根堆,保持k个最优点,最后输出堆顶元素的编号即可

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct point {
    ll dim[2], id;
}p[N];
struct tree {
    ll l, r, mx[2], mn[2], id;
    point p;
}tr[N];
struct node {
    ll dis, id;
    bool operator<(const node a)const {
        return dis == a.dis ? id < a.id : dis > a.dis;
    }
};
priority_queue<node> q;
int root, tot, now;
bool cmp(point a, point b) {
    return a.dim[now] < b.dim[now];
}
void pushup(int x) {
    for (int i = 0; i < 2; ++i) {
        tr[x].mn[i] = tr[x].mx[i] = tr[x].p.dim[i];
        if (tr[x].l) {
            tr[x].mn[i] = min(tr[x].mn[i], tr[tr[x].l].mn[i]);
            tr[x].mx[i] = max(tr[x].mx[i], tr[tr[x].l].mx[i]);
        }
        if (tr[x].r) {
            tr[x].mn[i] = min(tr[x].mn[i], tr[tr[x].r].mn[i]);
            tr[x].mx[i] = max(tr[x].mx[i], tr[tr[x].r].mx[i]);
        }
    }
}
int build(int l, int r, int d) {
    if (l > r) return 0;
    int x = ++tot;
    int mid = l + r >> 1;
    now = d;
    nth_element(p + l, p + mid, p + r + 1, cmp);
    tr[x].p = p[mid];
    tr[x].id = p[mid].id;
    tr[x].l = build(l, mid - 1, d ^ 1);
    tr[x].r = build(mid + 1, r, d ^ 1);
    pushup(x);
    return x;
}
ll sq(ll x) {
    return x * x;
}
ll dis(point a, point b) {
    return sq(a.dim[0] - b.dim[0]) + sq(a.dim[1] - b.dim[1]);
}
ll getdis(int x, point a) {
    ll res = 0;
    for (int i = 0; i < 2; ++i) {
        res += max(sq(a.dim[i] - tr[x].mx[i]), sq(a.dim[i] - tr[x].mn[i]));
    }
    return res;
}
void query(int x, point a) {
    if (!x) return;
    ll res = dis(tr[x].p, a);
    if (res > q.top().dis || res == q.top().dis && tr[x].id < q.top().id) {//找到更优点
        q.pop();
        q.push({ res,tr[x].id });
    }
    ll ld = INF, rd = INF;
    if (tr[x].l) ld = getdis(tr[x].l, a);
    if (tr[x].r) rd = getdis(tr[x].r, a);
    if (ld < rd) {//右边远,先查右边
        if (rd >= q.top().dis) query(tr[x].r, a);
        if (ld >= q.top().dis) query(tr[x].l, a);
    }
    else {
        if (ld > q.top().dis) query(tr[x].l, a);
        if (rd > q.top().dis) query(tr[x].r, a);
    }
}
void solve() {
    int n, m, k;
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        cin >> p[i].dim[0] >> p[i].dim[1];
        p[i].id = i;
    }
    root = build(1, n, 0);
    cin >> m;
    for (int i = 1; i <= m; ++i) {
        point a;
        cin >> a.dim[0] >> a.dim[1] >> k;
        while (!q.empty()) q.pop();
        for (int j = 1; j <= k; ++j) q.push({ -1,0 });
        query(root, a);
        cout << q.top().id << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

平面最近邻居问题。已知平面上n个点的坐标,且每个点有一个权值,做m次查询,每次查询给出一个坐标点u和一个k值,求距离u最近的权值小于k的点。
平面上n个点,两点之间的距离定义为曼哈顿距离。有m次查询,每次查询给定两个点,问两点的距离有多远。
有n个四维空间中的点,求出一条最长的路径,满足任意维坐标都是单调不降的。

49.LCT

将原树剖分成实链和虚边。实链是一条从上到下的路径,实链间通过虚边连接。将每条实链按节点深度构造成一棵splay树,链顶对应splay树最左节点,splay 根节点的父亲为该实链顶端节点的父亲

P3690 【模板】动态树(LCT)
给定 n n n 个点以及每个点的权值,要你处理接下来的 m m m 个操作。
操作有四种,操作从 0 0 0 3 3 3 编号。点从 1 1 1 n n n 编号。

  • 0 x y 代表询问从 x x x y y y 的路径上的点的权值的 xor \text{xor} xor 和。保证 x x x y y y 是联通的。
  • 1 x y 代表连接 x x x y y y,若 x x x y y y 已经联通则无需连接。
  • 2 x y 代表删除边 ( x , y ) (x,y) (x,y),不保证边 ( x , y ) (x,y) (x,y) 存在。
  • 3 x y 代表将点 x x x 上的权值变成 y y y

O ( l o g n ) O(logn) O(logn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 998244353;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    int l, r,fa, key, tag, sum;
}tr[N];
int st[N];
void pushup(int x) {
    tr[x].sum = tr[tr[x].l].sum ^ tr[tr[x].r].sum ^ tr[x].key;
}
void pushtag(int x) {//翻转
    swap(tr[x].l, tr[x].r);
    tr[x].tag ^= 1;
}
void pushdown(int x) {
    if (tr[x].tag) {
        pushtag(tr[x].l);
        pushtag(tr[x].r);
        tr[x].tag = 0;
    }
}
bool isroot(int x) {//判断是否为splay树根
    return tr[tr[x].fa].l != x && tr[tr[x].fa].r != x;
}
void rotate(int x) {
    int y = tr[x].fa, z = tr[y].fa;
    int k = (x == tr[y].r);
    if (!isroot(y)) {
        if (tr[z].l == y) tr[z].l = x;
        else tr[z].r = x;
    }
    if (k == 1) {
        tr[y].r = tr[x].l;
        tr[tr[x].l].fa = y;
        tr[x].l = y;
    }
    else {
        tr[y].l = tr[x].r;
        tr[tr[x].r].fa = y;
        tr[x].r = y;
    }
    tr[y].fa = x;
    tr[x].fa = z;
    pushup(y), pushup(x);
}
void splay(int x) {//把x旋至splay树的根
    int top = 0, p = x;
    st[++top] = p;
    while (!isroot(p)) st[++top] = p = tr[p].fa;
    while (top) pushdown(st[top--]);
    while (!isroot(x)) {
        int y = tr[x].fa, z = tr[y].fa;
        if (!isroot(y)) {
            if ((tr[y].r == x) == (tr[z].r == y)) rotate(y);
            else rotate(x);
        }
        rotate(x);
    }
}
void access(int x) {//把点x到根的路径“打通”,就是把点x到根划到同一棵splay中,在连接的同时把其它链断开。
    int z = x;
    for (int y = 0; x; y = x, x = tr[x].fa){//从x沿虚边走到根
        splay(x);
        tr[x].r = y;//建立实边
        pushup(x);
    }
    splay(z);
}
void makeroot(int x) {//将x变为原树的根
    access(x);
    pushtag(x);//翻转splay左右,即颠倒原树该实链上下
}
int findroot(int x) {//查询x在原树上的根
    access(x);
    while (tr[x].l) x = tr[x].l;
    splay(x);
    return x;
}
void split(int x, int y) {//在原树上以x为起点,y为终点生成一条实链
    makeroot(x);
    access(y);
}
void link(int x, int y) {//在x和y间连一条边
    makeroot(x);
    if (findroot(y) != x) tr[x].fa = y;
}
void cut(int x, int y) {//断开x和y间边
    makeroot(x);
    if (findroot(y) == x && tr[y].fa == x && !tr[y].l) {
        tr[x].r = tr[y].fa = 0;
        pushup(x);
    }
}
void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) cin >> tr[i].key;
    for (int i = 1; i <= m; ++i) {
        int op, x, y;
        cin >> op >> x >> y;
        if (op == 0) {
            split(x, y);
            cout << tr[y].sum << '\n';
        }
        else if (op == 1) link(x, y);
        else if (op == 2) cut(x, y);
        else {
            splay(x);
            tr[x].key = y;
            pushup(x);
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

判断连通性
求两点间距离
求LCA

50.左偏树

对于一棵二叉树,定义外节点为左儿子或右儿子为空的节点,定义一个外节点的 dist 为 1,一个不是外节点的节点 dist 为其到子树中最近的外节点的距离加一。
左偏树是一棵二叉树,它不仅具有堆的性质,并且是「左偏」的:每个节点左儿子的 dist 都大于等于右儿子的 dist。
因此,左偏树每个节点的 dist 都等于其右儿子的 dist 加一。

P3377 【模板】左偏树(可并堆)
如题,一开始有 n n n 个小根堆,每个堆包含且仅包含一个数。接下来需要支持两种操作:

  1. 1 x y:将第 x x x 个数和第 y y y 个数所在的小根堆合并(若第 x x x 或第 y y y 个数已经被删除或第 x x x 和第 y y y 个数在用一个堆内,则无视此操作)。
  2. 2 x:输出第 x x x 个数所在的堆最小数,并将这个最小数删除(若有多个最小数,优先删除先输入的;若第 x x x 个数已经被删除,则输出 − 1 -1 1 并无视删除操作)。

O ( l o g n ) O(logn) O(logn)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct {
    int l, r, key, dis;
    bool del;//是否已被删除
}tr[N];
int f[N];//并查集
bool cmp(int x, int y) {
    if (tr[x].key != tr[y].key) return tr[x].key < tr[y].key;
    else return x < y;
}
int find(int x) {
    if (f[x] == x) return x;
    else return f[x] = find(f[x]);
}
int merge(int x, int y) {
    if (!x || !y) return x + y;
    if (!cmp(x, y)) swap(x, y);//保证值x<y
    tr[x].r = merge(tr[x].r, y);//x为根(最小值),继续合并x右子树
    if (tr[tr[x].l].dis < tr[tr[x].r].dis) swap(tr[x].l, tr[x].r);//保证左偏
    tr[x].dis = tr[tr[x].r].dis + 1;
    return x;
}
void solve() {
    int n, m;
    cin >> n >> m;
    tr[0].key = 2e9;
    for (int i = 1; i <= n; ++i) {
        cin >> tr[i].key;
        tr[i].dis = 1;
        f[i] = i;
    }
    for (int i = 1; i <= m; ++i) {
        int op;
        cin >> op;
        if (op == 1) {
            int x, y;
            cin >> x >> y;
            if (tr[x].del || tr[y].del) continue;
            x = find(x), y = find(y);
            if (x != y) {
                if (!cmp(x, y)) swap(x, y);//保证值x<y
                merge(x, y);
                f[y] = x;
            }
        }
        else {
            int x;
            cin >> x;
            if (tr[x].del) {
                cout << -1 << '\n';
                continue;
            }
            x = find(x);
            cout << tr[x].key << '\n';
            if (!cmp(tr[x].l, tr[x].r)) swap(tr[x].l, tr[x].r);//保证值tr[x].l<tr[x].r
            merge(tr[x].l, tr[x].r);//tr[x].l为新根(最小值)
            tr[x].del = 1;
            f[x] = tr[x].l;//并查集换根
            f[tr[x].l] = tr[x].l;
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

51.DLX

精确覆盖问题

AcWing1067精确覆盖问题
给定一个 N×M 的数字矩阵 A,矩阵中的元素 A i , j A_{i,j} Ai,j∈{0,1}。
请问,你能否在矩阵中找到一个行的集合,使得这些行中,每一列都有且仅有一个数字 1。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 6e3 + 10;
struct node {
    int l, r, u, d, row, col;//lrud左右上下下一个1位置,row,col该点所在行,列
}p[N];
int n, m, cnt[N], tot, ans[N], top;//cnt该列1的个数
void init() {
    for (int i = 0; i <= m; ++i) {
        p[i].l = i - 1, p[i].r = i + 1;
        p[i].u = p[i].d = i;
    }
    p[0].l = m, p[m].r = 0;
    tot = m + 1;
}
void add(int& hh, int& tt, int x, int y) {
    p[tot].row = y, p[tot].col = x, cnt[x]++;
    p[tot].l = hh, p[tot].r = tt;
    p[hh].r = p[tt].l = tot;
    p[tot].u = x, p[tot].d = p[x].d;
    p[x].d = p[p[x].d].u = tot;
    tt = tot++;
}
void remove(int x) {//删除第x列及该列为1的每一行
    p[p[x].l].r = p[x].r, p[p[x].r].l = p[x].l;
    for (int i = p[x].d; i != x; i = p[i].d) {
        for (int j = p[i].r; j != i; j = p[j].r) {
            cnt[p[j].col]--;
            p[p[j].d].u = p[j].u, p[p[j].u].d = p[j].d;
        }
    }
}
void resume(int x) {//恢复第x列及相关行
    for (int i = p[x].u; i != x; i = p[i].u) {
        for (int j = p[i].l; j != i; j = p[j].l) {
            cnt[p[j].col]++;
            p[p[j].d].u = j, p[p[j].u].d = j;
        }
    }
    p[p[x].l].r = x, p[p[x].r].l = x;
}
bool dfs() {
    if (!p[0].r) return 1;//矩阵为空,满足
    int mn = p[0].r;
    for (int i = p[0].r; i; i = p[i].r) {//找1个数最少的列
        if (cnt[i] < cnt[mn]) mn = i;
    }
    remove(mn);//删除改列
    for (int i = p[mn].d; i != mn; i = p[i].d) {//遍历mn所有为1的行
        ans[++top] = p[i].row;//假定选择该行
        for (int j = p[i].r; j != i; j = p[j].r) remove(p[j].col);//删除这一行中为1的列
        if (dfs()) return 1;//递归
        for (int j = p[i].l; j != i; j = p[j].l) resume(p[j].col);//恢复
        top--;
    }
    resume(mn);//恢复该列
    return 0;
}
void solve() {
    cin >> n >> m;
    init();
    for (int i = 1; i <= n; ++i) {
        int hh = tot, tt = tot;
        for (int j = 1; j <= m; ++j) {
            int x;
            cin >> x;
            if (x) add(hh, tt, j, i);//插入十字链表并更新上下左右值
        }
    }
    if (dfs()) {
        for (int i = 1; i <= top; ++i) cout << ans[i] << " ";
    }
    else cout << "No Solution!";
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

数独问题

AcWing169数独2
请你将一个 16×16 的数独填写完整,使得每行、每列、每个 4×4 十六宫格内字母 A∼P 均恰好出现一次。
保证每个输入只有唯一解决方案。

思路:将原树剖分成实链和虚边。实链是一条从上到下的路径,实链间通过虚边连接。将每条实链按节点深度构造成一棵splay树,链顶对应splay树最左节点,splay 根节点的父亲为该实链顶端节点的父亲仔细剖析数独,数独其实只有四个约束条件——

  1. 每行恰有 16 个不同的数;
  2. 每列恰有 16 个不同的数;
  3. 每个宫每恰有 16 个不同的数;
  4. 每个位置恰好有一个数。

所以,如果我们把数独每个位置裂成四个点,每个点对应一个条件,数独要求就变成了每个位置填一个适当的数,使全部条件都有且只有一个点满足。
用三元组 (i,j,k) 表示在 (i,j) 上的数是 k,对这个问题建模,每个可能的三元组构成 01 矩阵的一行,每个约束条件构成 01 矩阵的一列。
对于列,具体地说,有 16×16=256 列记录数独每个数是否出现,256 列记录数独 16 行每行 16 个数是否出现,256 列记录数独 16 列每列 16 个数是否出现,256 列记录数独 16 宫 16 个数是否出现,
一个完成的数独应当由 16×16=256 个三元组表出,并且每个三元组满足四个约束条件。换句话说,目标是选出 256 行,每行 4 个 1,并且每列恰好 1 个 1,这就转化成了 DLX 问题。
对于数独已确定的位置占一个三元组 ( i , j , a i , j ) (i,j,a_{i,j}) (i,j,ai,j),对于不确定的位置有 16 种可能,用 16 个三元组表示。
行的数量(最多):16×16×16=4096,列的数量:16×16×4=1024。只需枚举数独所有可能的情况,使用 DLX 算法判断是否可行。
所有数独问题都可以这么转化,特别的,当多解的数独不同位置有权重时,也可以直接记录每个节点的权重,找到答案后不返回,选择是否更新答案即可

#include <bits/stdc++.h>
using namespace std;
const int N = 20000;
int m = 16 * 16 * 4, u[N], d[N], l[N], r[N], s[N], col[N], row[N], idx, ans[N], top;
struct Op{
    int x, y;
    char z;
}op[N];
char g[20][20];
void init(){
    for (int i = 0; i <= m; i++){
        l[i] = i - 1, r[i] = i + 1;
        s[i] = 0;
        d[i] = u[i] = i;
    }
    l[0] = m, r[m] = 0;
    idx = m + 1;
}
void add(int& hh, int& tt, int x, int y){
    row[idx] = x, col[idx] = y, s[y] ++;
    u[idx] = y, d[idx] = d[y], u[d[y]] = idx, d[y] = idx;
    r[hh] = l[tt] = idx, r[idx] = tt, l[idx] = hh;
    tt = idx++;
}
void remove(int p){
    r[l[p]] = r[p], l[r[p]] = l[p];
    for (int i = d[p]; i != p; i = d[i]) {
        for (int j = r[i]; j != i; j = r[j]) {
            s[col[j]] --;
            u[d[j]] = u[j], d[u[j]] = d[j];
        }
    }
}
void resume(int p){
    for (int i = u[p]; i != p; i = u[i]) {
        for (int j = l[i]; j != i; j = l[j]) {
            u[d[j]] = j, d[u[j]] = j;
            s[col[j]] ++;
        }
    }
    r[l[p]] = p, l[r[p]] = p;
}
bool dfs(){
    if (!r[0]) return true;
    int p = r[0];
    for (int i = r[0]; i; i = r[i]) {
        if (s[i] < s[p]) p = i;
    }
    remove(p);
    for (int i = d[p]; i != p; i = d[i]){
        ans[++top] = row[i];
        for (int j = r[i]; j != i; j = r[j]) remove(col[j]);
        if (dfs()) return true;
        for (int j = l[i]; j != i; j = l[j]) resume(col[j]);
        top--;
    }
    resume(p);
    return false;
}
int main(){
    while (~scanf("%s", g[0])){
        for (int i = 1; i < 16; i++) scanf("%s", g[i]);
        init();
        for (int i = 0, n = 1; i < 16; i++) {
            for (int j = 0; j < 16; j++) {
                int a = 0, b = 15;
                if (g[i][j] != '-') a = b = g[i][j] - 'A';
                for (int k = a; k <= b; k++, n++) {
                    int hh = idx, tt = idx;
                    op[n] = { i, j, k + 'A' };
                    add(hh, tt, n, i * 16 + j + 1);
                    add(hh, tt, n, 256 + i * 16 + k + 1);
                    add(hh, tt, n, 256 * 2 + j * 16 + k + 1);
                    add(hh, tt, n, 256 * 3 + (i / 4 * 4 + j / 4) * 16 + k + 1);
                }
            }
        }
        dfs();
        for (int i = 1; i <= top; i++){
            auto t = op[ans[i]];
            g[t.x][t.y] = t.z;
        }
        for (int i = 0; i < 16; i++) puts(g[i]);
        puts("");
    }
    return 0;
}

重复覆盖问题

AcWing2713重复覆盖问题
给定一个 N×M 的数字矩阵 A,矩阵中的元素 A i , j A_{i,j} Ai,j∈{0,1}。
请你在矩阵中找到一个行的集合,使得这些行中,每一列都包含数字 1,并且集合中包含的行数尽可能少。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e4 + 10;
struct node {
    int l, r, u, d, row, col;
}p[N];
int n, m, cnt[N], tot, ans[N], st[110];
void init() {
    for (register int i = 0; i <= m; ++i) {
        p[i].l = i - 1, p[i].r = i + 1;
        p[i].col = p[i].u = p[i].d = i;
    }
    p[0].l = m, p[m].r = 0;
    tot = m + 1;
}
void add(int& hh, int& tt, int x, int y) {
    p[tot].col = x, p[tot].row = y, cnt[x]++;
    p[tot].u = x, p[tot].d = p[x].d;
    p[x].d = p[p[x].d].u = tot;
    p[hh].r = p[tt].l = tot;
    p[tot].l = hh, p[tot].r = tt;
    tt = tot++;
}
int h() {//估价函数,预估最少还需要选择多少行才能完全覆盖所有列
    int cnt = 0;
    for (register int i = 1; i <= m; ++i) st[i] = 0;
    for (register int i = p[0].r; i; i = p[i].r) {
        if (st[p[i].col]) continue;
        cnt++;
        st[p[i].col] = 1;
        for (register int j = p[i].d; j != i; j = p[j].d) {
            for (register int k = p[j].r; k != j; k = p[k].r) st[p[k].col] = 1;
        }
    }
    return cnt;
}
void remove(int x) {
    for (register int i = p[x].d; i != x; i = p[i].d) {
        p[p[i].l].r = p[i].r, p[p[i].r].l = p[i].l;
    }
}
void resume(int x) {
    for (register int i = p[x].u; i != x; i = p[i].u) {
        p[p[i].l].r = i, p[p[i].r].l = i;
    }
}
bool dfs(int k, int dep) {
    if (k + h() > dep) return 0;//若当前选择的行数+最少还要选择的行数>最多能选择的行数,无解
    if (!p[0].r) return 1;
    int mn = p[0].r;
    for (register int i = p[0].r; i; i = p[i].r) {
        if (cnt[i] < cnt[mn])  mn = i;
    }
    for (register int i = p[mn].d; i != mn; i = p[i].d) {
        ans[k] = p[i].row;
        remove(i);
        for (register int j = p[i].r; j != i; j = p[j].r) remove(j);;
        if (dfs(k + 1, dep)) return 1;
        for (register int j = p[i].l; j != i; j = p[j].l) resume(j);
        resume(i);
    }
    return 0;
}
void solve() {
    cin >> n >> m;
    init();
    for (register int i = 1; i <= n; ++i) {
        int hh = tot, tt = tot;
        for (register int j = 1; j <= m; ++j) {
            int x;
            cin >> x;
            if (x) add(hh, tt, j, i);
        }
    }
    int dep = 0;
    while (!dfs(0, dep)) dep++;//迭代加深
    cout << dep << '\n';
    for (int i = 0; i < dep; ++i) cout << ans[i] << " ";
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

52.CDQ分治

三维偏序

P3810 【模板】三维偏序(陌上花开)
n n n 个元素,第 i i i 个元素有 a i , b i , c i a_i,b_i,c_i ai,bi,ci 三个属性,设 f ( i ) f(i) f(i) 表示满足 a j ≤ a i a_j \leq a_i ajai b j ≤ b i b_j \leq b_i bjbi c j ≤ c i c_j \leq c_i cjci j ≠ i j \ne i j=i j j j 的数量。
对于 d ∈ [ 0 , n ) d \in [0, n) d[0,n) ,求 f ( i ) = d f(i) = d f(i)=d 的数量。

思路:先按 a 维排序,再将左、右子区间按 b 维归并排序。虽然现在 a 的顺序被打乱了,但是前半边还是都小于后半边的,所以要是只计算前半边对后半边的偏序关系,是不会受到 a 的影响的。考虑到 b 已经排序了,直接双指针,树状数组统计 c 维贡献即可。

O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e5 + 10;
struct node {
    int a, b, c, cnt, ans;
    bool operator<(node t) {
        if (a != t.a) return a < t.a;
        else if (b != t.b) return b < t.b;
        else return c < t.c;
    }
    bool operator==(node t) {
        return a == t.a && b == t.b && c == t.c;
    }
}p[N], tmp[N];
int n, k, ans[N], c[N];
int lowbit(int x) {
    return x & -x;
}
void add(int x, int y) {
    for (; x <= k; x += lowbit(x))  c[x] += y;
}
int query(int x) {
    int res = 0;
    for (; x; x -= lowbit(x)) res += c[x];
    return res;
}
void mergesort(int l, int r) {//对b归并排序
    if (l >= r) return;
    int mid = l + r >> 1;
    mergesort(l, mid), mergesort(mid + 1, r);
    int L = l, R = mid + 1;
    int len = 0;
    while (L <= mid && R <= r) {
        if (p[L].b <= p[R].b) add(p[L].c, p[L].cnt), tmp[++len] = p[L], L++;//c加入树状数组
        else p[R].ans += query(p[R].c), tmp[++len] = p[R], R++;
    }
    while (L <= mid) add(p[L].c, p[L].cnt), tmp[++len] = p[L], L++;
    while (R <= r) p[R].ans += query(p[R].c), tmp[++len] = p[R], R++;//处理剩余
    for (int i = l; i <= mid; ++i) add(p[i].c, -p[i].cnt);//清空树状数组
    for (int i = l, j = 1; j <= len; ++i, ++j) p[i] = tmp[j];
}
void solve() {
    cin >> n >> k;
    for (int i = 1; i <= n; ++i) {
        cin >> p[i].a >> p[i].b >> p[i].c;
        p[i].cnt = 1;
    }
    sort(p + 1, p + n + 1);//按a排序
    int len = 0;
    for (int i = 1; i <= n; ++i) {//去重+统计重复次数
        if (p[i] == p[len]) p[len].cnt++;
        else p[++len] = p[i];
    }
    mergesort(1, len);
    for (int i = 1; i <= len; ++i) ans[p[i].ans + p[i].cnt - 1] += p[i].cnt;
    for (int i = 0; i < n; ++i) cout << ans[i] << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

53.01Trie

P4551 最长异或路径
给定一棵 n n n 个点的带权树,结点下标从 1 1 1 开始到 n n n。寻找树中找两个结点,求最长的异或路径。
异或路径指的是指两个结点之间唯一路径上的所有边权的异或。

思路:对于每一位进行贪心,如果这一位有一个与它不同的,即异或后是1,那我们就顺着这条路往下走;否则就顺着原路往下走。

O ( n ∗ l e n ) O(n*len) O(nlen)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 2e6 + 10;
struct edge {
    int u, v, w;
};
vector<vector<edge> >e(N);
int tr[N][2], tot, sum[N];//sum点到根异或和
void insert(int k) {
    int p = 0;
    for (int i = 30; i >= 0; --i) {
        int x = (k >> i) & 1;
        if (!tr[p][x]) tr[p][x] = ++tot;
        p = tr[p][x];
    }
}
int query(int k) {
    int p = 0, res = 0;
    for (int i = 30; i >= 0; --i) {
        int x = (k >> i) & 1;
        if (tr[p][!x]) {
            res += (1 << i);
            p = tr[p][!x];
        }
        else p = tr[p][x];
    }
    return res;
}
void dfs(int x, int fa) {
    for (auto i : e[x]) {
        if (i.v == fa) continue;
        sum[i.v] = sum[x] ^ i.w;
        insert(sum[i.v]);
        dfs(i.v, x);
    }
}
void solve() {
    int n;
    cin >> n;
    for (int i = 1; i < n; ++i) {
        int u, v, w;
        cin >> u >> v >> w;
        e[u].push_back({ u,v,w });
        e[v].push_back({ v,u,w });
    }
    dfs(1, 0);
    insert(0);
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans = max(ans, query(sum[i]));
    }
    cout << ans;
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

54.可持久化01Trie

P4735 最大异或和
给定一个非负整数序列 { a } \{a\} {a},初始长度为 N N N
M M M 个操作,有以下两种操作类型:

  1. A x:添加操作,表示在序列末尾添加一个数 x x x,序列的长度 N N N 1 1 1
  2. Q l r x:询问操作,你需要找到一个位置 p p p,满足 l ≤ p ≤ r l \le p \le r lpr,使得: a [ p ] ⊕ a [ p + 1 ] ⊕ . . . ⊕ a [ N ] ⊕ x a[p] \oplus a[p+1] \oplus ... \oplus a[N] \oplus x a[p]a[p+1]...a[N]x 最大,输出最大值。

思路:异或满足可减性,所以可以维护前缀和,然后a[p] xor a[p+1] xor … xor a[n] xor x=s[p−1] xor s[n] xor x,然后就只要维护 s[]。
此时查询转变为:已知 val=s[n] xor x,求一个 p∈[l−1,r−1],使得 s[p] xor val 最大。
可以构建一颗可持久化 0/1 Trie,第 i 个版本为插入了 s[i] 后的 Trie 树。
每次查询,从根节点开始,贪心地选与这一位相反的值。
此外,还有一个 l−1 ≤ p ≤ r−1 的限制。先考虑 p ≤ r−1,查询第 r−1 个版本的 Trie 即可,因为此时不可能访问到r−1之后的s。再考虑 l−1 ≤ p。对每个节点维护一个 latest,表示子树中所有 s 值的下标的最大值。这样,在查询时只访问 latest ≥ l−1 的节点就行了。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e8 + 10;
int tr[N][2], tot, pre, root[N], cnt[N];//cnt节点的数字个数
void insert(int rt, int k) {//在rt基础上插入k
    int p = tot, q = rt;
    for (int i = 24; i >= 0; --i) {
        tr[p][0] = tr[q][0], tr[p][1] = tr[q][1];
        int x = (k >> i) & 1;
        tr[p][x] = ++tot;
        p = tr[p][x], q = tr[q][x];
        cnt[p] = cnt[q] + 1;
    }
}
int query(int p, int q, int k) {
    int res = 0;
    for (int i = 24; i >= 0; --i) {
        int x = (k >> i) & 1;
        if (cnt[tr[q][!x]] > cnt[tr[p][!x]]) {//保证l−1 ≤ p
            res += (1 << i);
            p = tr[p][!x], q = tr[q][!x];
        }
        else p = tr[p][x], q = tr[q][x];
    }
    return res;
}
void solve() {
    int n, m;
    cin >> n >> m;
    root[0] = ++tot;
    insert(0, 0);
    for (int i = 1; i <= n; ++i) {
        int x;
        cin >> x;
        pre ^= x;
        root[i] = ++tot;
        insert(root[i - 1], pre);
    }
    for (int i = 1; i <= m; ++i) {
        char op;
        cin >> op;
        if (op == 'A') {
            int x;
            cin >> x;
            ++n;
            pre ^= x;
            root[n] = ++tot;
            insert(root[n - 1], pre);
        }
        else {
            int l, r, x;
            cin >> l >> r >> x;
            if (l == 1) cout << query(0, root[r - 1], pre ^ x) << '\n';
            else cout << query(root[l - 2], root[r - 1], pre ^ x) << '\n';
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin >> _t;
    while (_t--) {
        solve();
    }
    return 0;
}

55.树同构

P5043 【模板】树同构([BJOI2015]树的同构)
树是一种很常见的数据结构。
我们把 N N N 个点, N − 1 N-1 N1 条边的连通无向图称为树。
若将某个点作为根,从根开始遍历,则其它的点都有一个前驱,这个树就成为有根树。
对于两个树 T 1 T_1 T1 T 2 T_2 T2,如果能够把树 T 1 T_1 T1 的所有点重新标号,使得树 T 1 T_1 T1 和树 T 2 T_2 T2 完全相同,那么这两个树是同构的。也就是说,它们具有相同的形态。
现在,给你 M M M 个无根树,请你把它们按同构关系分成若干个等价类。

树哈希

思路:求出以每个点为根时的哈希值,排序后比较。
还可以通过找重心的方式来优化复杂度。一棵树的重心最多只有两个,只需把以它(们)为根时的哈希值求出来即可。接下来,既可以分别比较这些哈希值,也可以在有一个重心时取它的哈希值作为整棵树的哈希值,有两个时则取其中较小(大)的。

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 55;
vector<vector<int> >e(N);
ll ans[N][N];
ll mask = 2333;
ll shift(ll x) {
    x ^= mask;
    x ^= x << 13;
    x ^= x >> 7;
    x ^= x << 17;
    x ^= mask;
    return x;
}
ll Hash(int x, int fa) {
    ll son[N], cnt = 0, ans = 0;
    for (int i : e[x]) {
        if (i == fa) continue;
        son[++cnt] = Hash(i, x);
    }
    sort(son + 1, son + cnt + 1);
    for (int i = 1; i <= cnt; ++i) ans = ans * 2333 + son[i];
    return ans * 2333 + 1;
    //for (int i = 1; i <= cnt; ++i) ans += shift(son[i]);
    //return ans + 1;
}
void solve() {
    int m;
    cin >> m;
    for (int i = 1; i <= m; ++i) {
        e.clear();
        e.resize(N);
        int n;
        cin >> n;
        for (int j = 1; j <= n; ++j) {
            int u;
            cin >> u;
            if (u) e[u].push_back(j), e[j].push_back(u);
        }
        for (int j = 1; j <= n; ++j) ans[i][j] = Hash(j, 0);//计算以每个点为根的哈希值
        sort(ans[i] + 1, ans[i] + n + 1);
        for (int j = 1; j <= i; ++j) {
            int k;
            for (k = 1; k <= n; ++k) {
                if (ans[j][k] != ans[i][k]) break;
            }
            if (k > n) {
                cout << j << '\n';
                break;
            }
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

AHU算法

思路:一段合法的欧拉序和一棵有根树唯一对应,而且一棵树的欧拉序是由它的子树的欧拉序拼接而成的。如果我们通过改变子树欧拉序拼接的顺序,从而获得了一段新的欧拉序,那么新欧拉序对应的树和原欧拉序对应的树同构。
对于无根树 T1和 T2,先分别找出它们的 所有重心。
如果这两棵无根树重心数量不同,那么这两棵树不同构。
如果这两颗无根树重心数量都为 1,那么如果有根树 T1和有根树 T2同构,那么无根树 T1和 T2同构,反之则不同构。
如果这两颗无根树重心数量都为 2,那么如果有根树 T1和有根树 T2同构 或者有根树 T1和 T2′ 同构,那么无根树 T1和 T2同构,反之则不同构。

O ( m n 2 ) O(mn^2) O(mn2)

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<int,int>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 55;
vector<vector<int> >e(N);
int m, n, mn,siz[N], zx[N], cnt;
string f[N], son[N], tmp, ans[N];
void dfs1(int x, int fa) {
    siz[x] = 1;
    int tmp = 0;
    for (int i : e[x]) {
        if (i == fa) continue;
        dfs1(i, x);
        siz[x] += siz[i];
        tmp = max(tmp, siz[i]);
    }
    tmp = max(tmp, n - siz[x]);
    if (tmp < mn) {
        mn = tmp;
        cnt = 0;
        zx[++cnt] = x;
    }
    else if (tmp == mn) zx[++cnt] = x;
}
void dfs2(int x, int fa) {
    f[x] = "0";
    for (int i : e[x]) {
        if (i == fa) continue;
        dfs2(i, x);
    }
    int tot = 0;
    for (int i : e[x]) {
        if (i == fa) continue;
        son[++tot] = f[i];
    }
    sort(son + 1, son + tot + 1);
    for (int i = 1; i <= tot; ++i) f[x] += son[i];
    f[x] += "1";
    return;
}
void solve() {
    cin >> m;
    for (int i = 1; i <= m; ++i) {
        e.clear();
        e.resize(N);
        tmp = "1", mn = 0x3f3f3f3f, cnt = 0;
        cin >> n;
        for (int j = 1; j <= n; ++j) {
            int u;
            cin >> u;
            if (u) e[u].push_back(j), e[j].push_back(u);
        }
        dfs1(1, 0);//求重心
        for (int j = 1; j <= cnt; ++j) {
            dfs2(zx[j], 0);
            tmp = min(tmp, f[zx[j]]);//求最小欧拉序
        }
        ans[i] = tmp;
        for (int j = 1; j <= i; ++j) {
            if (ans[j] == ans[i]) {
                cout << j << '\n';
                break;
            }
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //cin>>_t;
    while (_t--) {
        solve();
    }
    return 0;
}

56.树分块

树上撒点。设一个阈值 S,并考虑在树上选择不超过 n S \frac{n}{S} Sn 个点作为关键点,满足每个关键点到离它最近的祖先关键点的距离不超过S

P6177 Count on a tree II/【模板】树分块
给定一个 n n n 个节点的树,每个节点上有一个整数, i i i 号点的整数为 v a l i val_i vali
m m m 次询问,每次给出 u ′ , v u',v u,v,您需要将其解密得到 u , v u,v u,v,并查询 u u u v v v 的路径上有多少个不同的整数。
解密方式: u = u ′ xor ⁡ l a s t a n s u=u' \operatorname{xor} lastans u=uxorlastans
l a s t a n s lastans lastans 为上一次询问的答案,若无询问则为 0 0 0

每次选择一个深度最大的非关键点,若它的1∼S级祖先都不是关键点,则把它的S级祖先标记为关键点。考虑一条到根路径上的所有关键点,用bitset维护它们两两间出现的颜色。求出 u,v 的祖先中离 u,v 最近的关键点 u0,v0,以及离 lca 最近且在 t 子树中的关键点 u1,v1。那么一条路径被我们拆成了以下几个部分:u∼u0,v∼v0,u1∼lca,v1∼lca,u0∼u1,v0∼v1。对于前面 4 种,直接暴力跳。而对于后面两种,我们已经预处理它们的 bitset,所以直接取并集。
O ( ( n + m ) n + n 2 + n m w ) O((n+m)\sqrt n+\frac{n^2+nm}{w}) O((n+m)n +wn2+nm)

#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 5e4 + 10;
vector<vector<int> >e(N);
bitset<N> bit[210][210], ans;
int a[N], b[N], dep[N], mxdep[N], f[N][20], id[N], st[N], cnt, top, fa[N];
void dfs(int x, int father) {
    mxdep[x] = dep[x] = dep[father] + 1;
    f[x][0] = father;
    for (int i = 1; i < 20; ++i) f[x][i] = f[f[x][i - 1]][i - 1];
    for (int i : e[x]) {
        if (i == father) continue;
        dfs(i, x);
        mxdep[x] = max(mxdep[x], mxdep[i]);
    }
    //每次选择一个深度最大的非关键点,若它的1∼S级祖先都不是关键点,则把它的S级祖先标记为关键点
    if (mxdep[x] - dep[x] >= 200) {
        id[x] = ++cnt;
        mxdep[x] = dep[x];
    }
}
void dfs2(int x, int father) {//考虑一条到根路径上的所有关键点,用bitset维护它们两两间出现的颜色
    for (int i : e[x]) {
        if (i == father) continue;
        if (id[i]) {
            int u = id[st[top]], v = id[i];
            for (int j = i; j != st[top]; j = f[j][0]) bit[u][v].set(a[j]);
            for (int j = 1; j < top; ++j) bit[id[st[j]]][v] = bit[id[st[j]]][u] | bit[u][v];
            fa[i] = st[top];
            st[++top] = i;
        }
        dfs2(i, x);
        if (id[i]) top--;
    }
}
int lca(int x, int y) {
    if (dep[x] < dep[y]) swap(x, y);
    for (int i = 19; i >= 0; --i) {
        if (dep[f[x][i]] >= dep[y]) x = f[x][i];
    }
    if (x == y) return x;
    for (int i = 19; i >= 0; --i) {
        if (f[x][i] != f[y][i]) {
            x = f[x][i];
            y = f[y][i];
        }
    }
    return f[x][0];
}
inline void solve() {
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; ++i) cin >> a[i], b[i] = a[i];
    sort(b + 1, b + n + 1);
    int len = unique(b + 1, b + n + 1) - b - 1;
    for (int i = 1; i <= n; ++i) a[i] = lower_bound(b + 1, b + len + 1, a[i]) - b;
    for (int i = 1; i < n; ++i) {
        int u, v;
        cin >> u >> v;
        e[u].push_back(v);
        e[v].push_back(u);
    }
    dfs(1, 0);
    if (!id[1]) id[1] = ++cnt;
    st[top = 1] = 1;
    dfs2(1, 0);
    int last = 0;
    for (int i = 1; i <= m; ++i) {
        int u, v;
        cin >> u >> v;
        u ^= last;
        ans.reset();
        int d = lca(u, v);
        while (u != d && !id[u]) ans.set(a[u]), u = f[u][0];//u∼u0,v∼v0
        while (v != d && !id[v]) ans.set(a[v]), v = f[v][0];
        if (u != d) {
            int p = u;
            while (dep[fa[p]] >= dep[d]) p = fa[p];//u0∼u1,v0∼v1
            ans |= bit[id[p]][id[u]];
            while (p != d) ans.set(a[p]), p = f[p][0];//u1∼lca,v1∼lca
        }
        if (v != d) {
            int p = v;
            while (dep[fa[p]] >= dep[d]) p = fa[p];
            ans |= bit[id[p]][id[v]];
            while (p != d) ans.set(a[p]), p = f[p][0];
        }
        ans.set(a[d]);
        last = ans.count();
        cout << last << '\n';
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //_t = read();
    while (_t--) {
        solve();
    }
    return 0;
}

57.珂朵莉树

珂朵莉树是一种以近乎暴力的形式存储区间信息的一个数据结构。方式是通过set存放若干个用结构体表示的区间,每个区间的元素都是相同的。只要是涉及到区间赋值操作的题,就可以用珂朵莉树处理几乎任何关于区间信息的询问。珂朵莉树要求题目必须存在区间赋值操作,且数据有高度的随机性。

CF896C Willem, Chtholly and Seniorious
请你写一种奇怪的数据结构,支持:

  • 1 1 1 l l l r r r x x x :将 [ l , r ] [l,r] [l,r] 区间所有数加上 x x x
  • 2 2 2 l l l r r r x x x :将 [ l , r ] [l,r] [l,r] 区间所有数改成 x x x
  • 3 3 3 l l l r r r x x x :输出将 [ l , r ] [l,r] [l,r] 区间从小到大排序后的第 x x x 个数是的多少(即区间第 x x x 小,数字大小相同算多次,保证 1 ≤ 1\leq 1 x x x ≤ \leq r − l + 1 r-l+1 rl+1 )
  • 4 4 4 l l l r r r x x x y y y :输出 [ l , r ] [l,r] [l,r] 区间每个数字的 x x x 次方的和模 y y y 的值(即( ∑ i = l r a i x \sum^r_{i=l}a_i^x i=lraix ) m o d    y \mod y mody )
#include<bits/stdc++.h>
using namespace std;
#define ll long long 
#define P pair<ll,ll>
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const ll INF = 1e18;
const ll N = 1e5 + 10;
struct node {
    ll l, r;
    mutable ll v;//mutable关键字定义一个强制可变量
    bool operator<(const node a)const {
        return l < a.l;
    }
};
set<node> tr;
ll n, m, seed, vmax, a[N];
ll rnd() {
    ll ret = seed;
    seed = (seed * 7 + 13) % mod;
    return ret;
}
auto split(ll pos) {//从set中的区间中找到pos所在的区间,拆成两个区间
    auto it = tr.lower_bound({ pos,0,0 });
    if (it != tr.end() && it->l == pos) return it;
    it--;
    if (it->r < pos) return tr.end();
    ll l = it->l, r = it->r, v = it->v;
    tr.erase(it);//删除区间
    tr.insert({ l,pos - 1,v });//重新插入两个区间
    return tr.insert({ pos,r,v }).first;//返回以pos开头的区间的迭代器
}
void add(ll l, ll r, ll x) {
    auto itr = split(r + 1), itl = split(l);
    for (auto i = itl; i != itr; ++i) i->v += x;
}
void merge(ll l, ll r, ll x) {//区间赋值,合并
    auto itr = split(r + 1), itl = split(l);//先r+1,再l
    tr.erase(itl, itr);//删除这些区间
    tr.insert({ l,r,x });//重新插入区间
}
ll kth(ll l, ll r, ll k) {
    vector<P> v;
    auto itr = split(r + 1), itl = split(l);
    for (auto i = itl; i != itr; ++i) v.push_back({ i->v,i->r - i->l + 1 });
    sort(v.begin(), v.end());
    for (auto i : v) {
        k -= i.second;
        if (k <= 0) return i.first;
    }
}
ll ksm(ll a, ll b, ll p) {
    ll res = 1;
    a %= p;
    while (b) {
        if (b & 1) res = res * a % p;
        b >>= 1;
        a = a * a % p;
    }
    return res;
}
ll query(ll l, ll r, ll x, ll y) {
    ll res = 0;
    auto itr = split(r + 1), itl = split(l);
    for (auto i = itl; i != itr; ++i) (res += ksm(i->v, x, y) * (i->r - i->l + 1) % y) %= y;
    return res;
}
inline void solve() {
    cin >> n >> m >> seed >> vmax;
    for (int i = 1; i <= n; ++i) {
        a[i] = rnd() % vmax + 1;
        tr.insert({ i, i, a[i] });
    }
    for (int i = 1; i <= m; ++i) {
        ll op = rnd() % 4 + 1, l = rnd() % n + 1, r = rnd() % n + 1;
        if (l > r) swap(l, r);
        if (op == 1) {
            ll x = rnd() % vmax + 1;
            add(l, r, x);
        }
        else if (op == 2) {
            ll x = rnd() % vmax + 1;
            merge(l, r, x);
        }
        else if (op == 3) {
            ll x = rnd() % (r - l + 1) + 1;
            cout << kth(l, r, x) << '\n';
        }
        else {
            ll x = rnd() % vmax + 1, y = rnd() % vmax + 1;
            cout << query(l, r, x, y) << '\n';
        }
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int _t = 1;
    //_t = read();
    while (_t--) {
        solve();
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: Excel数据看板模板是一种可视化的数据分析工具,用于将复杂的数据整理、呈现和分析。它可以将大量的数据以图表、表格、图像等形式直观地展示,帮助用户快速了解数据的趋势、关联和异常。 Excel数据看板模板通常包括以下几个主要部分: 1. 数据输入区域:用于输入和更新数据的区域,用户可以根据需要自定义数据的输入方式和格式。 2. 数据分析区域:通过各种计算公式和函数对输入的数据进行分析和处理,例如平均值、总和、百分比等。 3. 图表区域:用于展示数据的可视化图表,如柱状图、折线图、饼状图等。用户可以选择合适的图表类型来展示不同的数据关系和趋势。 4. 指标区域:用于展示关键指标或目标的实时数值,可以通过条件格式和数据透视表进行灵活的设置和调整。 5. 仪表盘区域:通过仪表盘图表展示数据的整体情况和变化趋势,如速度表、指针图等。 使用Excel数据看板模板可以带来以下几个好处: 1. 可视化信息:通过图表和图像的形式展示数据,更直观地了解数据的情况和趋势。 2. 快速分析:通过预定义的公式和函数,可以快速对数据进行计算和分析,提高分析效率。 3. 灵活性和可定制性:模板可以根据用户的需求进行灵活的设置和调整,以适应不同的分析要求。 4. 数据共享和报告输出:可以将数据看板模板导出为报表或分享给他人,方便数据的共享和反馈。 综上所述,Excel数据看板模板是一种实用的数据分析工具,通过可视化展示和快速分析数据,帮助用户更好地理解数据,做出有针对性的决策。 ### 回答2: Excel数据看板模板是一种可视化展示数据的工具,适用于各种数据分析和报告需求。它提供了一个组织有序的布局,将大量数据呈现为易于理解的图表、表格和指标,使用户可以快速、直观地了解数据情况。 一个好的Excel数据看板模板应包含以下几个要素: 1. 数据源连接:通过连接外部数据源,如数据库、API或其他文件,实现数据的自动更新和实时展示,避免手动输入和更新数据的麻烦。 2. 数据透视表与图表:透视表可以帮助用户根据需要对数据进行分组、过滤和汇总,以便更好地理解数据的结构和特点。结合柱状图、折线图等图表形式,可以更直观地展示数据的趋势和变化。 3. 简洁明了的布局:合理的布局可以提高信息的可读性和易理解性。通过合理安排不同的模块和区域,避免信息的混乱和重叠,使用户在一眼就能找到所需信息。 4. 数据指标和KPI:通过设定关键绩效指标(KPI),用户可以随时了解数据达成情况。这些指标可以是有关销售额、利润、成本、用户数量等关键数据的计算结果,用以衡量业务的健康状况和发展趋势。 5. 筛选和交互功能:提供筛选器和交互式功能,可以按需选择特定数据集,实现数据的动态展示和对比分析。这样用户可以根据自己的关注点,通过调整筛选条件来探索不同的数据视角。 综上所述,Excel数据看板模板是一种通过可视化展示数据,使用户能够直观、简单地了解数据情况的工具。通过适当的布局、图表、指标和交互功能的设计,可以帮助用户更好地理解数据,做出准确的决策。 ### 回答3: Excel数据看板模板是一种用于可视化和分析数据的工具。它提供了一种简单而直观的方法,将大量数据以图表、表格和指标等形式展示出来,帮助用户理解和监控业务状况。 首先,Excel数据看板模板具有灵活性和可定制性。用户可以根据自己的需求和喜好,选择合适的表格样式、图表类型和指标设置。这使得看板能够适应不同行业和业务场景,并满足各种数据分析需求。 其次,Excel数据看板模板具有易于使用和操作的特点。无论是新手还是有经验的用户,都能够快速上手并进行数据监控和分析。用户只需输入或导入数据,然后按照模板的数据布局进行填充和编辑,即可生成看板报表。 此外,Excel数据看板模板还具有数据更新和实时监控的功能。用户可以将数据与外部数据源连接,使得看板能够实时更新数据。这对于需要经常关注和分析数据变化的用户来说,非常有用。 最后,Excel数据看板模板还支持数据导出和共享。用户可以将看板报表导出为Excel文件或图像文件,方便在其他平台或设备上查看和分享。此外,用户还可以将看板共享给其他用户,共同进行数据分析和决策。 总结而言,Excel数据看板模板是一个强大而实用的数据分析工具。它能够帮助用户清晰地呈现、分析和监控数据,提供决策支持和业务优化的依据。无论是个人还是企业,都可以通过使用Excel数据看板模板来提高数据分析的效率和准确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值