数据结构进阶课

高阶数据结构

Splay

AcWing 2437. Splay

AcWing 950. 郁闷的出纳员

AcWing 1063. 永无乡

AcWing 955. 维护数列

在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 500010, INF = 1e9;

int n, m;
struct Node
{
    int s[2], p, v;
    int rev, same;
    int size, sum, ms, ls, rs;

    void init(int _v, int _p)
    {
        s[0] = s[1] = 0, p = _p, v = _v;
        rev = same = 0;
        size = 1, sum = ms = v;
        ls = rs = max(v, 0);
    }
}tr[N];
int root, nodes[N], tt;
int w[N];

void pushup(int x)
{
    auto &u = tr[x], &l = tr[u.s[0]], &r = tr[u.s[1]];
    u.size = l.size + r.size + 1;
    u.sum = l.sum + r.sum + u.v;
    u.ls = max(l.ls, l.sum + u.v + r.ls);
    u.rs = max(r.rs, r.sum + u.v + l.rs);
    u.ms = max(max(l.ms, r.ms), l.rs + u.v + r.ls);
}

void pushdown(int x)
{
    auto &u = tr[x], &l = tr[u.s[0]], &r = tr[u.s[1]];
    if (u.same)
    {
        u.same = u.rev = 0;
        if (u.s[0]) l.same = 1, l.v = u.v, l.sum = l.v * l.size;
        if (u.s[1]) r.same = 1, r.v = u.v, r.sum = r.v * r.size;
        if (u.v > 0)
        {
            if (u.s[0]) l.ms = l.ls = l.rs = l.sum;
            if (u.s[1]) r.ms = r.ls = r.rs = r.sum;
        }
        else
        {
            if (u.s[0]) l.ms = l.v, l.ls = l.rs = 0;
            if (u.s[1]) r.ms = r.v, r.ls = r.rs = 0;
        }
    }
    else if (u.rev)
    {
        u.rev = 0, l.rev ^= 1, r.rev ^= 1;
        swap(l.ls, l.rs), swap(r.ls, r.rs);
        swap(l.s[0], l.s[1]), swap(r.s[0], r.s[1]);
    }
}

void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

void splay(int x, int k)
{
    while (tr[x].p != k)
    {
        int y = tr[x].p, z = tr[y].p;
        if (z != k)
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
    if (!k) root = x;
}

int get_k(int k)
{
    int u = root;
    while (u)
    {
        pushdown(u);
        if (tr[tr[u].s[0]].size >= k) u = tr[u].s[0];
        else if (tr[tr[u].s[0]].size + 1 == k) return u;
        else k -= tr[tr[u].s[0]].size + 1, u = tr[u].s[1];
    }
}

int build(int l, int r, int p)
{
    int mid = l + r >> 1;
    int u = nodes[tt -- ];
    tr[u].init(w[mid], p);
    if (l < mid) tr[u].s[0] = build(l, mid - 1, u);
    if (mid < r) tr[u].s[1] = build(mid + 1, r, u);
    pushup(u);
    return u;
}

void dfs(int u)
{
    if (tr[u].s[0]) dfs(tr[u].s[0]);
    if (tr[u].s[1]) dfs(tr[u].s[1]);
    nodes[ ++ tt] = u;
}

int main()
{
    for (int i = 1; i < N; i ++ ) nodes[ ++ tt] = i;
    scanf("%d%d", &n, &m);
    tr[0].ms = w[0] = w[n + 1] = -INF;
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    root = build(0, n + 1, 0);

    char op[20];
    while (m -- )
    {
        scanf("%s", op);
        if (!strcmp(op, "INSERT"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            for (int i = 0; i < tot; i ++ ) scanf("%d", &w[i]);
            int l = get_k(posi + 1), r = get_k(posi + 2);
            splay(l, 0), splay(r, l);
            int u = build(0, tot - 1, r);
            tr[r].s[0] = u;
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "DELETE"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            dfs(tr[r].s[0]);
            tr[r].s[0] = 0;
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "MAKE-SAME"))
        {
            int posi, tot, c;
            scanf("%d%d%d", &posi, &tot, &c);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            auto& son = tr[tr[r].s[0]];
            son.same = 1, son.v = c, son.sum = c * son.size;
            if (c > 0) son.ms = son.ls = son.rs = son.sum;
            else son.ms = c, son.ls = son.rs = 0;
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "REVERSE"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            auto& son = tr[tr[r].s[0]];
            son.rev ^= 1;
            swap(son.ls, son.rs);
            swap(son.s[0], son.s[1]);
            pushup(r), pushup(l);
        }
        else if (!strcmp(op, "GET-SUM"))
        {
            int posi, tot;
            scanf("%d%d", &posi, &tot);
            int l = get_k(posi), r = get_k(posi + tot + 1);
            splay(l, 0), splay(r, l);
            printf("%d\n", tr[tr[r].s[0]].sum);
        }
        else printf("%d\n", tr[root].ms);
    }

    return 0;
}

树套树

AcWing 2488. 树套树-简单版

AcWing 2476. 树套树

在这里插入图片描述

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>

using namespace std;

const int N = 2000010, INF = 1e9;

int n, m;
struct Node
{
    int s[2], p, v;
    int size;

    void init(int _v, int _p)
    {
        v = _v, p = _p;
        size = 1;
    }
}tr[N];
int L[N], R[N], T[N], idx;
int w[N];

void pushup(int x)
{
    tr[x].size = tr[tr[x].s[0]].size + tr[tr[x].s[1]].size + 1;
}

void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    tr[z].s[tr[z].s[1] == y] = x, tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}

void splay(int& root, int x, int k)
{
    while (tr[x].p != k)
    {
        int y = tr[x].p, z = tr[y].p;
        if (z != k)
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
    if (!k) root = x;
}

void insert(int& root, int v)
{
    int u = root, p = 0;
    while (u) p = u, u = tr[u].s[v > tr[u].v];
    u = ++ idx;
    if (p) tr[p].s[v > tr[p].v] = u;
    tr[u].init(v, p);
    splay(root, u, 0);
}

int get_k(int root, int v)
{
    int u = root, res = 0;
    while (u)
    {
        if (tr[u].v < v) res += tr[tr[u].s[0]].size + 1, u = tr[u].s[1];
        else u = tr[u].s[0];
    }
    return res;
}

void update(int& root, int x, int y)
{
    int u = root;
    while (u)
    {
        if (tr[u].v == x) break;
        if (tr[u].v < x) u = tr[u].s[1];
        else u = tr[u].s[0];
    }
    splay(root, u, 0);
    int l = tr[u].s[0], r = tr[u].s[1];
    while (tr[l].s[1]) l = tr[l].s[1];
    while (tr[r].s[0]) r = tr[r].s[0];
    splay(root, l, 0), splay(root, r, l);
    tr[r].s[0] = 0;
    pushup(r), pushup(l);
    insert(root, y);
}

void build(int u, int l, int r)
{
    L[u] = l, R[u] = r;
    insert(T[u], -INF), insert(T[u], INF);
    for (int i = l; i <= r; i ++ ) insert(T[u], w[i]);
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

int query(int u, int a, int b, int x)
{
    if (L[u] >= a && R[u] <= b) return get_k(T[u], x) - 1;
    int mid = L[u] + R[u] >> 1, res = 0;
    if (a <= mid) res += query(u << 1, a, b, x);
    if (b > mid) res += query(u << 1 | 1, a, b, x);
    return res;
}

void change(int u, int p, int x)
{
    update(T[u], w[p], x);
    if (L[u] == R[u]) return;
    int mid = L[u] + R[u] >> 1;
    if (p <= mid) change(u << 1, p, x);
    else change(u << 1 | 1, p, x);
}

int get_pre(int root, int v)
{
    int u = root, res = -INF;
    while (u)
    {
        if (tr[u].v < v) res = max(res, tr[u].v), u = tr[u].s[1];
        else u = tr[u].s[0];
    }
    return res;
}

int get_suc(int root, int v)
{
    int u = root, res = INF;
    while (u)
    {
        if (tr[u].v > v) res = min(res, tr[u].v), u = tr[u].s[0];
        else u = tr[u].s[1];
    }
    return res;
}

int query_pre(int u, int a, int b, int x)
{
    if (L[u] >= a && R[u] <= b) return get_pre(T[u], x);
    int mid = L[u] + R[u] >> 1, res = -INF;
    if (a <= mid) res = max(res, query_pre(u << 1, a, b, x));
    if (b > mid) res = max(res, query_pre(u << 1 | 1, a, b, x));
    return res;
}

int query_suc(int u, int a, int b, int x)
{
    if (L[u] >= a && R[u] <= b) return get_suc(T[u], x);
    int mid = L[u] + R[u] >> 1, res = INF;
    if (a <= mid) res = min(res, query_suc(u << 1, a, b, x));
    if (b > mid) res = min(res, query_suc(u << 1 | 1, a, b, x));
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    build(1, 1, n);

    while (m -- )
    {
        int op, a, b, x;
        scanf("%d", &op);
        if (op == 1)
        {
            scanf("%d%d%d", &a, &b, &x);
            printf("%d\n", query(1, a, b, x) + 1);
        }
        else if (op == 2)
        {
            scanf("%d%d%d", &a, &b, &x);
            int l = 0, r = 1e8;
            while (l < r)
            {
                int mid = l + r + 1 >> 1;
                if (query(1, a, b, mid) + 1 <= x) l = mid;
                else r = mid - 1;
            }
            printf("%d\n", r);
        }
        else if (op == 3)
        {
            scanf("%d%d", &a, &x);
            change(1, a, x);
            w[a] = x;
        }
        else if (op == 4)
        {
            scanf("%d%d%d", &a, &b, &x);
            printf("%d\n", query_pre(1, a, b, x));
        }
        else
        {
            scanf("%d%d%d", &a, &b, &x);
            printf("%d\n", query_suc(1, a, b, x));
        }
    }

    return 0;
}

AcWing 2306. K大数查询

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 50010, P = N * 17 * 17, M = N * 4;

int n, m;
struct Tree
{
    int l, r, sum, add;
}tr[P];
int L[M], R[M], T[M], idx;
struct Query
{
    int op, a, b, c;
}q[N];
vector<int> nums;

int get(int x)
{
    return lower_bound(nums.begin(), nums.end(), x) - nums.begin();
}

void build(int u, int l, int r)
{
    L[u] = l, R[u] = r, T[u] = ++ idx;
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

int intersection(int a, int b, int c, int d)
{
    return min(b, d) - max(a, c) + 1;
}

void update(int u, int l, int r, int pl, int pr)
{
    tr[u].sum += intersection(l, r, pl, pr);
    if (l >= pl && r <= pr)
    {
        tr[u].add ++ ;
        return;
    }
    int mid = l + r >> 1;
    if (pl <= mid)
    {
        if (!tr[u].l) tr[u].l = ++ idx;
        update(tr[u].l, l, mid, pl, pr);
    }
    if (pr > mid)
    {
        if (!tr[u].r) tr[u].r = ++ idx;
        update(tr[u].r, mid + 1, r, pl, pr);
    }
}

void change(int u, int a, int b, int c)
{
    update(T[u], 1, n, a, b);
    if (L[u] == R[u]) return;
    int mid = L[u] + R[u] >> 1;
    if (c <= mid) change(u << 1, a, b, c);
    else change(u << 1 | 1, a, b, c);
}

int get_sum(int u, int l, int r, int pl, int pr, int add)
{
    if (l >= pl && r <= pr) return tr[u].sum + (r - l + 1) * add;
    int mid = l + r >> 1, res = 0;
    add += tr[u].add;
    if (pl <= mid)
    {
        if (tr[u].l) res += get_sum(tr[u].l, l, mid, pl, pr, add);
        else res += intersection(l, mid, pl, pr) * add;
    }
    if (pr > mid)
    {
        if (tr[u].r) res += get_sum(tr[u].r, mid + 1, r, pl, pr, add);
        else res += intersection(mid + 1, r, pl, pr) * add;
    }
    return res;
}

int query(int u, int a, int b, int c)
{
    if (L[u] == R[u]) return R[u];
    int mid = L[u] + R[u] >> 1;
    int k = get_sum(T[u << 1 | 1], 1, n, a, b, 0);
    if (k >= c) return query(u << 1 | 1, a, b, c);
    return query(u << 1, a, b, c - k);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i ++ )
    {
        scanf("%d%d%d%d", &q[i].op, &q[i].a, &q[i].b, &q[i].c);
        if (q[i].op == 1) nums.push_back(q[i].c);
    }
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    build(1, 0, nums.size() - 1);

    for (int i = 0; i < m; i ++ )
    {
        int op = q[i].op, a = q[i].a, b = q[i].b, c = q[i].c;
        if (op == 1) change(1, a, b, get(c));
        else printf("%d\n", nums[query(1, a, b, c)]);
    }

    return 0;
}

1782. 动态排名

在这里插入图片描述

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<map>
using namespace std;
const int N = 100010, M = 100010;
struct operation {
    int kind, l, r, k, x, y, ans;
    // kind = 1表示是查询操作,否则是修改操作
}q[M];
int n, m;
int a[N], b[N << 1], len = 0, t[N << 1];
void msort(int head, int tail) {
    if (head == tail) return;
    int mid = (head + tail) >> 1;
    msort(head, mid); msort(mid + 1, tail);
    int pointer1 = head, pointer2 = mid + 1;
    for (int i = 0; i < tail - head + 1; i++) {
        if (pointer1 <= mid && pointer2 <= tail) {
            if (b[pointer1] < b[pointer2]) t[i] = b[pointer1++];
            else t[i] = b[pointer2++];
        }
        else if (pointer1 <= mid)t[i] = b[pointer1++];
        else t[i] = b[pointer2++];
    }
    for (int i = 0; i < tail - head + 1; i++) b[i + head] = t[i];
}
const int S = N * 400;
int leftson[S], rightson[S], sum[S], cnt = 0, root[N];
/*
* 我们接下来要利用树状数组的思想来构造线段树
* 也就是说root[i]这棵线段树负责的是a数组中的某些项的信息
* 比如说root[i]里面有一个节点是u,u所负责的区间是l ~ r,那么sum[u]记录的就是
* a数组中的某些项在l ~ r里面出现了几次。
* 那么究竟是a数组的哪些项呢?这个就是树状数组的思想了,a[x]这一项会出现在下面这个循环所确定的i所对应的root[i]中
* for(int i = x; i <= n ; i += lowbit(i))
* 所以如果你想知道a[1], a[2], ... a[x]这1 ~ x项有多少个是出现在l ~ r里面,那么你就应该在下面的这些root[i]中去查找
* for(int i = x; i > 0 ; i -= lowbit(i))
*/
int store[2][N], number[2];
/*
这个store是干什么的呢,就比如说我们现在要查询a[left] ~ a[right]里面排第k的数
肯定是用二分法来做吧,假设我们手头的区间是[L, R],并且我们已经知道了a[left] ~ a[right]里面小于L的数有res这么多个
那么我们无非是想看一下mid = (L + R) >> 1能不能做a[left] ~ a[right]里面排第k的数嘛,那就是要找出来a[left] ~ a[right]
里面有多少个数是<= mid的,因为排第k个数要满足a[left] ~ a[right]里面小于等于他的至少有k个,而且他是满足这个条件的最小的数
这个证明很好证的,我就不写了。
所以我们只要求出来a[1] ~ a[left - 1]里面有多少个数是<= mid的,再用a[1] ~ a[right]里面有多少个数是<= mid的去减一下就好了
由于我们又知道a[left] ~ a[right]里面< L 的数有res这么多个,所以我们只要找出来a[1] ~ a[left - 1], a[1] ~ a[right]里面有多少个数是处于
[L, mid]这个区间就可以了,然后你就发现我们需要的是a[1], a[2], a[3], ... a[left - 1]有多少个出现在L ~ mid中,那么你需要的线段树就是下面这些
for(int i = left - 1; i > 0; i -= lowbit(i)),我们把这个循环确定的i存在store[0]里面
同理你还需要for(int i = right; i > 0; i -= lowbit(i)),把这个循环确定的i存在store[1]里面
number[0]和number[1]当然就是分别存储这两个循环牵涉到了多少棵线段树咯
而且我们可以把二分的过程跟查找的过程结合起来。
*/
void modify(int u, int l, int r, int x, int c) {
    sum[u] += c;
    if (l == r) return;
    int mid = (l + r) >> 1;
    if (x <= mid) {
        if (leftson[u] == 0) leftson[u] = ++cnt;
        modify(leftson[u], l, mid, x, c);
    }
    else {
        if (rightson[u] == 0) rightson[u] = ++cnt;
        modify(rightson[u], mid + 1, r, x, c);
    }
}
// 你会发现这里是不需要pushup的,因为这是单点的修改呀,就是l ~ r里面的第x这个位置要多c而已
int lowbit(int i) {
    return i & -i;
}
void lookfortrees(int left, int right) {
    number[0] = 0; number[1] = 0;
    for (int i = left - 1; i > 0; i -= lowbit(i)) store[0][++number[0]] = root[i];
    for (int i = right; i > 0; i -= lowbit(i)) store[1][++number[1]] = root[i];
}
void query(int L, int R, int pos) {
    // 我们现在是利用二分法来给第pos个操作找答案,当然了这说明第pos个操作肯定是查询操作
    if (L == R) { q[pos].ans = b[L]; return; }
    // 我们保证,现在的store里面存的线段树的根节点负责的区间刚好就是L ~ R
    int res = 0;
    int mid = (L + R) >> 1;
    for (int i = 1; i <= number[0]; i++) res -= sum[leftson[store[0][i]]];
    for (int i = 1; i <= number[1]; i++) res += sum[leftson[store[1][i]]];
    if (res >= q[pos].k) {
        // 如果res >= q[pos].k的话说明应该往左边走吧
        for (int i = 1; i <= number[0];i++) store[0][i] = leftson[store[0][i]];
        for (int i = 1; i <= number[1]; i++) store[1][i] = leftson[store[1][i]];
        query(L, mid, pos);
    }
    else {
        q[pos].k -= res; // 这一步我不想再解释了,就当成整体二分模板的一部分吧
        for (int i = 1; i <= number[0]; i++) store[0][i] = rightson[store[0][i]];
        for (int i = 1; i <= number[1]; i++) store[1][i] = rightson[store[1][i]];
        query(mid + 1, R, pos);
    }
    // 上面这个操作确实很巧妙,充分利用了查找的二分区间,跟线段树负责的区间是可以同调的
}

int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
        b[++len] = a[i];
    }
    for (int i = 1; i <= m; i++) {
        char op = 0; cin >> op;
        if (op == 'Q') {
            scanf("%d%d%d", &q[i].l, &q[i].r, &q[i].k);
            q[i].kind = 1;
        }
        else {
            scanf("%d%d", &q[i].x, &q[i].y);
            b[++len] = q[i].y;
        }
    }
    sort(b + 1, b + 1 + len);len = unique(b + 1, b + len + 1) - b - 1;
    for (int x = 1; x <= n; x++) {
        for (int i = x; i <= n; i += lowbit(i)) {
            if (root[i] == 0) root[i] = ++cnt;
            int p = lower_bound(b + 1, b + len + 1, a[x]) - b;
            modify(root[i], 1, len, p, 1);
        }
    }
    for (int i = 1; i <= m; i++) {
        if (q[i].kind == 1) {
            // 这是查询操作
            lookfortrees(q[i].l, q[i].r);
            query(1, len, i);
            printf("%d\n", q[i].ans);
        }
        else {
            // 否则就是修改操作
            int x = q[i].x;
            int p = lower_bound(b + 1, b + len + 1, a[x]) - b;
            for (int i = x; i <= n; i += lowbit(i)) {
                if (root[i] == 0) root[i] = ++cnt;
                modify(root[i], 1, len, p, -1);
            }
            a[x] = q[i].y;
            p = lower_bound(b + 1, b + len + 1, a[x]) - b;
            for (int i = x; i <= n; i += lowbit(i)) {
                if (root[i] == 0) root[i] = ++cnt;
                modify(root[i], 1, len, p, 1);
            }
        }
    }
}

2997. 不勤劳的图书管理员

在这里插入图片描述

// P3759 [TJOI2017]不勤劳的图书管理员
//二维线段树 
#include<bits/stdc++.h>
#define Pli pair<ll,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
typedef long long ll;
const ll sz=50050,N=sz*256,mod=1e9+7,RNK=51000;
void Plus(Pli &x,Pli y){x.fir+=y.fir;x.sec+=y.sec;}
/**************************内层线段树**************************/
//权值线段树,书本优先级为权值 
ll size[N];//书页数 
int ch[N][2],cnt[N],tot;//儿子节点,书数,动态开点计数 
queue<int>rec;//垃圾回收 
#define lson ch[k][0],l,mid
#define rson ch[k][1],mid+1,r
void destroy(int &k){size[k]=cnt[k]=ch[k][0]=ch[k][1]=0;rec.push(k);k=0;}
int newnode()
{
    if (rec.empty()) return ++tot;
    int ret=rec.front();
    rec.pop();
    return ret;
}
void add(int &k,int l,int r,int pos,ll x,int y)//于pos处插入x页书,y本书,可以为负 
{
    if (!k) k=newnode();
    size[k]+=x;cnt[k]+=y;
    size[k]=(size[k]+mod)%mod;
    if (l==r)
    {
    	if (!cnt[k]) destroy(k);
    	return;
    }
    int mid=(l+r)>>1;
    if (pos<=mid) add(lson,pos,x,y);
    else add(rson,pos,x,y);
    if (!cnt[k]) destroy(k);
}
Pli Query(int k,int l,int r,int x,int y)//x~y区间书页数、书数 
{
    if (!k) return mp(0ll,0ll);
    if (x<=l&&r<=y) return mp(size[k],cnt[k]);
    int mid=(l+r)>>1;
    Pli ret;
    ret.fir=0ll;ret.sec=0;
    if (x<=mid) Plus(ret,Query(lson,x,y));
    if (y>mid) Plus(ret,Query(rson,x,y));
    return ret;
}
#undef lson
#undef rson
/*************************外层线段树***************************/ 
#define lson k<<1,l,mid
#define rson k<<1|1,mid+1,r
int root[N];
void insert(int k,int l,int r,int pos,int x,int sum)
{
    add(root[k],1,RNK,x,sum,1);
    if (l==r) return;
    int mid=(l+r)>>1;
    if (pos<=mid) insert(lson,pos,x,sum);
    else insert(rson,pos,x,sum);
}
void del(int k,int l,int r,int pos,int x,int sum)
{
    add(root[k],1,RNK,x,-sum,-1);//负数,即删除 
    if (l==r) return;
    int mid=(l+r)>>1;
    if (pos<=mid) del(lson,pos,x,sum);
    else del(rson,pos,x,sum);
}
Pli Ask(int k,int l,int r,int x,int y,int L,int R)
//x~y(位置区间)内L~R(权值区间)的书页数、书数  
{
    if (x<=l&&r<=y) return Query(root[k],1,RNK,L,R);
    int mid=(l+r)>>1;
    Pli ret;
    ret.fir=0ll;ret.sec=0;
    if (x<=mid) Plus(ret,Ask(lson,x,y,L,R));
    if (y>mid) Plus(ret,Ask(rson,x,y,L,R));
    ret.fir%=mod;
    return ret;
}
#undef lson
#undef rson
/************************************************************/ 
int a[sz],n,m;
ll sum[sz],ans;
//a[i] i位置上的书的优先级
//sum[i] i位置上的书的书页数 
ll read()
{
    register ll ret=0;
    register char ch=getchar();
    while (ch>'9'||ch<'0') ch=getchar();
    while (ch<='9'&&ch>='0') ret=(ret<<1)+(ret<<3)+(ch^48),ch=getchar();
    return ret;
}
int main()
{
 	register int i,x,y;
 	n=read();m=read();
 	for (i=1;i<=n;i++) a[i]=read(),sum[i]=read()%mod;
 	for (i=1;i<=n;i++) insert(1,1,n,i,a[i],sum[i]);
 	for (i=1;i<=n;i++)
 	{
 		Pli cur=Ask(1,1,n,i+1,n,1,a[i]);
 		ans+=sum[i]*cur.sec+cur.fir;
 		ans%=mod;
 	}
    while(m--)
    {
        x=read(),y=read();
        if(x>y) swap(x,y);
        if(x==y){printf("%lld\n",ans);continue;}//并没有交换 
        int L=a[x],R=a[y],cnt,mn=min(L,R),mx=max(L,R);
        ll size;
        Pli cur;
        
        //x~y(位置)中权值大于mn而小于mx的书会产生贡献 
        cur=Ask(1,1,n,x,y,mn+1,mx-1);
        size=cur.fir,cnt=cur.sec;
        ans+=(mn==L?1:-1)*(2*size%mod+(ll)(cnt+1)*(sum[x]+sum[y])%mod)%mod;
        ans%=mod;
        
        //x~y(位置)中权值小于mn的书会产生贡献 
        cur=Ask(1,1,n,x,y,1,mn-1);
        cnt=cur.sec;
        (ans+=(ll)cnt*(sum[y]-sum[x])%mod)%=mod;
        
        //x~y(位置)中权值大于mx的书会产生贡献 
        cur=Ask(1,1,n,x,y,mx+1,n);
        cnt=cur.sec;
        (ans+=(ll)cnt*(sum[x]-sum[y])%mod)%=mod;
        
        if(ans<0) ans+=mod;
        
 		del(1,1,n,x,a[x],sum[x]);del(1,1,n,y,a[y],sum[y]);//删掉原来的 
 		insert(1,1,n,x,a[y],sum[y]);insert(1,1,n,y,a[x],sum[x]);//加上新的 
        swap(a[x],a[y]);swap(sum[x],sum[y]);//换 
        
        printf("%lld\n",ans);
    }
}

不想写或者不会写数据结构的用分块就好,然后有两种写法,一种是对每一块排序,一种是对每块维护一个树状数组。

这样分L和R在同一块,L和R所在的两个不完整的块,L和R跨过的完整的块三种情况讨论即可。

注意!!这题前两种情况一定不能暴力重建块!暴力重建的用时是1200+s,如果只更新变化的部分就只要80+s。

还有一个问题,因为整块标记的复杂度是O(nSlogn)的(其中S是块的大小),所以S设为nlogn−−−−−√的速度是设为n−−√的两倍。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
typedef long long ll;
#define rep(i,l,r) for (register int i=l; i<=r; i++)
using namespace std;

const ll N=50100,md=1000000007;
int n,m,mx,L,R,B,a[N],v[N],sz[N],bl[N],cnt,sum[N],d[300],c[250][N],c1[250][N],ans;

void add(int c[],int x,int k){ for (; x<=n; x+=x&-x) c[x]=(c[x]+k)%md; }
int que(int c[],int x){ ll res=0; for (; x; x-=x&-x) res=(res+c[x])%md; return res; }
void add1(int c1[],int x,int k){ for (; x<=n; x+=x&-x) c1[x]=(c1[x]+k)%md; }
int que1(int c1[],int x){ ll res=0; for (; x; x-=x&-x) res=(res+c1[x])%md; return res; }
int get(int x){ return x>=0 ? x : x+md; }

void cal(int x,int y,int z){
    if (a[x]<a[z]) ans=(ans+v[x]+v[z])%md; else ans=(ans-v[x]-v[z]+md+md)%md;
    if (a[y]<a[z]) ans=(ans+md+md-v[y]-v[z])%md; else ans=(ans+v[y]+v[z])%md;
}

void work(int L,int R){
    if (L==R) return;
    if (bl[L]==bl[R]){
        rep(i,L+1,R-1) cal(L,R,i); swap(a[L],a[R]); swap(v[L],v[R]);
    }else{
        rep(i,L+1,(bl[L]-1)*B+sz[bl[L]]) cal(L,R,i);
        rep(i,(bl[R]-1)*B+1,R-1) cal(L,R,i);
        swap(a[L],a[R]); swap(v[L],v[R]);
        sum[bl[L]]=(sum[bl[L]]+md-v[R]+v[L])%md; sum[bl[R]]=(sum[bl[R]]+md-v[L]+v[R])%md;
        add(c[bl[R]],a[L],-v[L]); add(c[bl[L]],a[R],-v[R]); add1(c1[bl[R]],a[L],-1); add1(c1[bl[L]],a[R],-1);
        add(c[bl[L]],a[L],v[L]); add(c[bl[R]],a[R],v[R]); add1(c1[bl[L]],a[L],1); add1(c1[bl[R]],a[R],1);
        rep(i,bl[L]+1,bl[R]-1){
            ans=get((1ll*ans-(que(c[i],a[R]-1)+1ll*que1(c1[i],a[R]-1)*v[R])%md));
            ans=get((1ll*ans+(que(c[i],a[L]-1)+1ll*que1(c1[i],a[L]-1)*v[L]))%md);
            ans=get((1ll*ans-(sum[i]-que(c[i],a[L])+1ll*(sz[i]-que1(c1[i],a[L]))*v[L]))%md);
            ans=get((1ll*ans+(sum[i]-que(c[i],a[R])+1ll*(sz[i]-que1(c1[i],a[R]))*v[R]))%md);
        }
    }
    if (a[R]<a[L]) ans=(1ll*ans+v[L]+v[R])%md; else ans=get((1ll*ans-(v[L]+v[R]))%md);
}

int main(){
    freopen("book.in","r",stdin);
    freopen("book.out","w",stdout);
    scanf("%d%d",&n,&m); B=(int)sqrt(n*17);
    rep(i,1,n) scanf("%d%d",&a[i],&v[i]);
    rep(i,1,n) bl[i]=(i-1)/B+1; mx=(n-1)/B+1;
    rep(i,1,mx-1) sz[i]=B; sz[mx]=n-(mx-1)*B;
    rep(i,1,n) add(c[bl[i]],a[i],v[i]),add1(c1[bl[i]],a[i],1),sum[bl[i]]=(sum[bl[i]]+v[i])%md;
    rep(i,1,n){
        ans=get((1ll*ans+(i-1-1ll*que1(c1[0],a[i]-1))*v[i]+cnt-que(c[0],a[i]-1))%md);
        add(c[0],a[i],v[i]); add1(c1[0],a[i],1); cnt=(cnt+v[i])%md;
    }
    rep(i,1,m) scanf("%d%d",&L,&R),work(min(L,R),max(L,R)),printf("%d\n",ans);
    return 0;
}

分块之基本思想

AcWing 243. 一个简单的整数问题2

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cmath>

using namespace std;

typedef long long LL;
const int N = 100010, M = 350;

int n, m, len;
LL add[M], sum[M];
int w[N];

int get(int i)
{
    return i / len;
}

void change(int l, int r, int d)
{
    if (get(l) == get(r))  // 段内直接暴力
    {
        for (int i = l; i <= r; i ++ ) w[i] += d, sum[get(i)] += d;
    }
    else
    {
        int i = l, j = r;
        while (get(i) == get(l)) w[i] += d, sum[get(i)] += d, i ++ ;
        while (get(j) == get(r)) w[j] += d, sum[get(j)] += d, j -- ;
        for (int k = get(i); k <= get(j); k ++ ) sum[k] += len * d, add[k] += d;
    }
}

LL query(int l, int r)
{
    LL res = 0;
    if (get(l) == get(r))  // 段内直接暴力
    {
        for (int i = l; i <= r; i ++ ) res += w[i] + add[get(i)];
    }
    else
    {
        int i = l, j = r;
        while (get(i) == get(l)) res += w[i] + add[get(i)], i ++ ;
        while (get(j) == get(r)) res += w[j] + add[get(j)], j -- ;
        for (int k = get(i); k <= get(j); k ++ ) res += sum[k];
    }
    return res;
}

int main()
{
    scanf("%d%d", &n, &m);
    len = sqrt(n);
    for (int i = 1; i <= n; i ++ )
    {
        scanf("%d", &w[i]);
        sum[get(i)] += w[i];
    }

    char op[2];
    int l, r, d;
    while (m -- )
    {
        scanf("%s%d%d", op, &l, &r);
        if (*op == 'C')
        {
            scanf("%d", &d);
            change(l, r, d);
        }
        else printf("%lld\n", query(l, r));
    }

    return 0;
}

分块之块状链表

AcWing 947. 文本编辑器

在这里插入图片描述

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 2000, M = 2010;

int n, x, y;
struct Node
{
    char s[N + 1];
    int c, l, r;
}p[M];
char str[2000010];
int q[M], tt;  // 内存回收

void move(int k)  // 移到第k个字符后面
{
    x = p[0].r;
    while (k > p[x].c) k -= p[x].c, x = p[x].r;
    y = k - 1;
}

void add(int x, int u)  // 将节点u插到节点x的右边
{
    p[u].r = p[x].r, p[p[u].r].l = u;
    p[x].r = u, p[u].l = x;
}

void del(int u)  // 删除节点u
{
    p[p[u].l].r = p[u].r;
    p[p[u].r].l = p[u].l;
    p[u].l = p[u].r = p[u].c = 0;  // 清空节点u
    q[ ++ tt] = u;  // 回收节点u
}

void insert(int k)  // 在光标后插入k个字符
{
    if (y < p[x].c - 1)  // 从光标处分裂
    {
        int u = q[tt -- ];  // 新建一个节点
        for (int i = y + 1; i < p[x].c; i ++ )
            p[u].s[p[u].c ++ ] = p[x].s[i];
        p[x].c = y + 1;
        add(x, u);
    }
    int cur = x;
    for (int i = 0; i < k;)
    {
        int u = q[tt -- ];  // 创建一个新的块
        while (p[u].c < N && i < k)
            p[u].s[p[u].c ++ ] = str[i ++ ];
        add(cur, u);
        cur = u;
    }
}

void remove(int k)  // 删除光标后的k个字符
{
    if (p[x].c - 1 - y >= k)  // 节点内删
    {
        for (int i = y + k + 1, j = y + 1; i < p[x].c; i ++, j ++ ) p[x].s[j] = p[x].s[i];
        p[x].c -= k;
    }
    else
    {
        k -= p[x].c - y - 1;  // 删除当前节点的剩余部分
        p[x].c = y + 1;
        while (p[x].r && k >= p[p[x].r].c)
        {
            int u = p[x].r;
            k -= p[u].c;
            del(u);
        }
        int u = p[x].r;  // 删除结尾节点的前半部分
        for (int i = 0, j = k; j < p[u].c; i ++, j ++ ) p[u].s[i] = p[u].s[j];
        p[u].c -= k;
    }
}

void get(int k)  // 返回从光标开始的k个字符
{
    if (p[x].c - 1 - y >= k)  // 节点内返回
    {
        for (int i = 0, j = y + 1; i < k; i ++, j ++ ) putchar(p[x].s[j]);
    }
    else
    {
        k -= p[x].c - y - 1;
        for (int i = y + 1; i < p[x].c; i ++ ) putchar(p[x].s[i]);  // 输出当前节点的剩余部分
        int cur = x;
        while (p[cur].r && k >= p[p[cur].r].c)
        {
            int u = p[cur].r;
            for (int i = 0; i < p[u].c; i ++ ) putchar(p[u].s[i]);
            k -= p[u].c;
            cur = u;
        }
        int u = p[cur].r;
        for (int i = 0; i < k; i ++ ) putchar(p[u].s[i]);
    }
    puts("");
}

void prev()  // 光标向前移动一位
{
    if (!y)
    {
        x = p[x].l;
        y = p[x].c - 1;
    }
    else y -- ;
}

void next()  // 光标向后移动一位
{
    if (y < p[x].c - 1) y ++ ;
    else
    {
        x = p[x].r;
        y = 0;
    }
}

void merge()  // 将长度较短的相邻节点合并,保证块状链表时间复杂度的核心
{
    for (int i = p[0].r; i; i = p[i].r)
    {
        while (p[i].r && p[i].c + p[p[i].r].c < N)
        {
            int r = p[i].r;
            for (int j = p[i].c, k = 0; k < p[r].c; j ++, k ++ )
                p[i].s[j] = p[r].s[k];
            if (x == r) x = i, y += p[i].c;  // 更新光标的位置
            p[i].c += p[r].c;
            del(r);
        }
    }
}

int main()
{
    for (int i = 1; i < M; i ++ ) q[ ++ tt] = i;
    scanf("%d", &n);
    char op[10];

    str[0] = '>';
    insert(1);  // 插入哨兵
    move(1);  // 将光标移动到哨兵后面

    while (n -- )
    {
        int a;
        scanf("%s", op);
        if (!strcmp(op, "Move"))
        {
            scanf("%d", &a);
            move(a + 1);
        }
        else if (!strcmp(op, "Insert"))
        {
            scanf("%d", &a);
            int i = 0, k = a;
            while (a)
            {
                str[i] = getchar();
                if (str[i] >= 32 && str[i] <= 126) i ++, a -- ;
            }
            insert(k);
            merge();
        }
        else if (!strcmp(op, "Delete"))
        {
            scanf("%d", &a);
            remove(a);
            merge();
        }
        else if (!strcmp(op, "Get"))
        {
            scanf("%d", &a);
            get(a);
        }
        else if (!strcmp(op, "Prev")) prev();
        else next();
    }

    return 0;
}

莫队之基础莫队

AcWing 2492. HH的项链

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 50010, M = 200010, S = 1000010;

int n, m, len;
int w[N], ans[M];
struct Query
{
    int id, l, r;
}q[M];
int cnt[S];

int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

void add(int x, int& res)
{
    if (!cnt[x]) res ++ ;
    cnt[x] ++ ;
}

void del(int x, int& res)
{
    cnt[x] -- ;
    if (!cnt[x]) res -- ;
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    scanf("%d", &m);
    len = max(1, (int)sqrt((double)n * n / m));

    for (int i = 0; i < m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i] = {i, l, r};
    }
    sort(q, q + m, cmp);

    for (int k = 0, i = 0, j = 1, res = 0; k < m; k ++ )
    {
        int id = q[k].id, l = q[k].l, r = q[k].r;
        while (i < r) add(w[ ++ i], res);
        while (i > r) del(w[i -- ], res);
        while (j < l) del(w[j ++ ], res);
        while (j > l) add(w[ -- j], res);
        ans[id] = res;
    }

    for (int i = 0; i < m; i ++ ) printf("%d\n", ans[i]);
    return 0;
}

莫队之带修改的莫队

AcWing 2521. 数颜色

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 10010, S = 1000010;

int n, m, mq, mc, len;
int w[N], cnt[S], ans[N];
struct Query
{
    int id, l, r, t;
}q[N];
struct Modify
{
    int p, c;
}c[N];

int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int al = get(a.l), ar = get(a.r);
    int bl = get(b.l), br = get(b.r);
    if (al != bl) return al < bl;
    if (ar != br) return ar < br;
    return a.t < b.t;
}

void add(int x, int& res)
{
    if (!cnt[x]) res ++ ;
    cnt[x] ++ ;
}

void del(int x, int& res)
{
    cnt[x] -- ;
    if (!cnt[x]) res -- ;
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    for (int i = 0; i < m; i ++ )
    {
        char op[2];
        int a, b;
        scanf("%s%d%d", op, &a, &b);
        if (*op == 'Q') mq ++, q[mq] = {mq, a, b, mc};
        else c[ ++ mc] = {a, b};
    }

    len = cbrt((double)n * mc) + 1;
    sort(q + 1, q + mq + 1, cmp);

    for (int i = 0, j = 1, t = 0, k = 1, res = 0; k <= mq; k ++ )
    {
        int id = q[k].id, l = q[k].l, r = q[k].r, tm = q[k].t;
        while (i < r) add(w[ ++ i], res);
        while (i > r) del(w[i -- ], res);
        while (j < l) del(w[j ++ ], res);
        while (j > l) add(w[ -- j], res);
        while (t < tm)
        {
            t ++ ;
            if (c[t].p >= j && c[t].p <= i)
            {
                del(w[c[t].p], res);
                add(c[t].c, res);
            }
            swap(w[c[t].p], c[t].c);
        }
        while (t > tm)
        {
            if (c[t].p >= j && c[t].p <= i)
            {
                del(w[c[t].p], res);
                add(c[t].c, res);
            }
            swap(w[c[t].p], c[t].c);
            t -- ;
        }
        ans[id] = res;
    }

    for (int i = 1; i <= mq; i ++ ) printf("%d\n", ans[i]);
    return 0;
}

莫队之回滚莫队

AcWing 2523. 历史研究

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

typedef long long LL;
const int N = 100010;

int n, m, len;
int w[N], cnt[N];
LL ans[N];
struct Query
{
    int id, l, r;
}q[N];
vector<int> nums;

int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

void add(int x, LL& res)
{
    cnt[x] ++ ;
    res = max(res, (LL)cnt[x] * nums[x]);
}

int main()
{
    scanf("%d%d", &n, &m);
    len = sqrt(n);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]), nums.push_back(w[i]);
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    for (int i = 1; i <= n; i ++ )
        w[i] = lower_bound(nums.begin(), nums.end(), w[i]) - nums.begin();

    for (int i = 0; i < m; i ++ )
    {
        int l, r;
        scanf("%d%d", &l, &r);
        q[i] = {i, l, r};
    }
    sort(q, q + m, cmp);

    for (int x = 0; x < m;)
    {
        int y = x;
        while (y < m && get(q[y].l) == get(q[x].l)) y ++ ;
        int right = get(q[x].l) * len + len - 1;

        // 暴力求块内的询问
        while (x < y && q[x].r <= right)
        {
            LL res = 0;
            int id = q[x].id, l = q[x].l, r = q[x].r;
            for (int k = l; k <= r; k ++ ) add(w[k], res);
            ans[id] = res;
            for (int k = l; k <= r; k ++ ) cnt[w[k]] -- ;
            x ++ ;
        }

        // 求块外的询问
        LL res = 0;
        int i = right, j = right + 1;
        while (x < y)
        {
            int id = q[x].id, l = q[x].l, r = q[x].r;
            while (i < r) add(w[ ++ i], res);
            LL backup = res;
            while (j > l) add(w[ -- j], res);
            ans[id] = res;
            while (j < right + 1) cnt[w[j ++ ]] -- ;
            res = backup;
            x ++ ;
        }
        memset(cnt, 0, sizeof cnt);
    }

    for (int i = 0; i < m; i ++ ) printf("%lld\n", ans[i]);
    return 0;
}

莫队之树上莫队

AcWing 2534. 树上计数2

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;

const int N = 100010;

int n, m, len;
int w[N];
int h[N], e[N], ne[N], idx;
int depth[N], f[N][16];
int seq[N], top, first[N], last[N];
int cnt[N], st[N], ans[N];
int que[N];
struct Query
{
    int id, l, r, p;
}q[N];
vector<int> nums;

void add_edge(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u, int father)
{
    seq[ ++ top] = u;
    first[u] = top;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j != father) dfs(j, u);
    }
    seq[ ++ top] = u;
    last[u] = top;
}

void bfs()
{
    memset(depth, 0x3f, sizeof depth);
    depth[0] = 0, depth[1] = 1;
    int hh = 0, tt = 0;
    que[0] = 1;
    while (hh <= tt)
    {
        int t = que[hh ++ ];
        for (int i = h[t]; ~i; i = ne[i])
        {
            int j = e[i];
            if (depth[j] > depth[t] + 1)
            {
                depth[j] = depth[t] + 1;
                f[j][0] = t;
                for (int k = 1; k <= 15; k ++ )
                    f[j][k] = f[f[j][k - 1]][k - 1];
                que[ ++ tt] = j;
            }
        }
    }
}

int lca(int a, int b)
{
    if (depth[a] < depth[b]) swap(a, b);
    for (int k = 15; k >= 0; k -- )
        if (depth[f[a][k]] >= depth[b])
            a = f[a][k];
    if (a == b) return a;
    for (int k = 15; k >= 0; k -- )
        if (f[a][k] != f[b][k])
        {
            a = f[a][k];
            b = f[b][k];
        }
    return f[a][0];
}

int get(int x)
{
    return x / len;
}

bool cmp(const Query& a, const Query& b)
{
    int i = get(a.l), j = get(b.l);
    if (i != j) return i < j;
    return a.r < b.r;
}

void add(int x, int& res)
{
    st[x] ^= 1;
    if (st[x] == 0)
    {
        cnt[w[x]] -- ;
        if (!cnt[w[x]]) res -- ;
    }
    else
    {
        if (!cnt[w[x]]) res ++ ;
        cnt[w[x]] ++ ;
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]), nums.push_back(w[i]);
    sort(nums.begin(), nums.end());
    nums.erase(unique(nums.begin(), nums.end()), nums.end());
    for (int i = 1; i <= n; i ++ )
        w[i] = lower_bound(nums.begin(), nums.end(), w[i]) - nums.begin();

    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add_edge(a, b), add_edge(b, a);
    }

    dfs(1, -1);
    bfs();

    for (int i = 0; i < m; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        if (first[a] > first[b]) swap(a, b);
        int p = lca(a, b);
        if (a == p) q[i] = {i, first[a], first[b]};
        else q[i] = {i, last[a], first[b], p};
    }

    len = sqrt(top);
    sort(q, q + m, cmp);

    for (int i = 0, L = 1, R = 0, res = 0; i < m; i ++ )
    {
        int id = q[i].id, l = q[i].l, r = q[i].r, p = q[i].p;
        while (R < r) add(seq[ ++ R], res);
        while (R > r) add(seq[R -- ], res);
        while (L < l) add(seq[L ++ ], res);
        while (L > l) add(seq[ -- L], res);
        if (p) add(p, res);
        ans[id] = res;
        if (p) add(p, res);
    }

    for (int i = 0; i < m; i ++ ) printf("%d\n", ans[i]);

    return 0;
}

莫队之二次离线莫队

AcWing 2535. 二次离线莫队

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>

using namespace std;
typedef long long ll;
const int N = 500010, M = 5000007, INF = 0x3f3f3f3f;
int read()
{
    int x = 0, f = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-')f = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();}
    return x * f;
}

int n, m;
int block, k;
int w[N];
ll ans[N];

struct Query
{
    int id, l, r;
    ll res;
}q[N];

struct Range
{
    int id, l, r, t;//t表示的是类型+/-
};

vector<Range>range[N];
int f[N], g[N];

//得到x的二进制下有多少个1
inline int get_count(int x)
{
    int res = 0;
    while(x)res += x & 1, x >>= 1;
    return res;
}

inline int get_block(int x)
{
    return x / block;
}

bool cmp(const Query& a, const Query& b)
{
    int x = get_block(a.l);
    int y = get_block(b.l);
    if(x != y)return x < y;
    return a.r < b.r;
}

int main()
{
    n = read(), m = read(), k = read();
    for(int i = 1; i <= n; ++ i)
        w[i] = read();
    vector<int> nums;
    //预处理出给定范围内所有二进制数为k的数
    for(int i = 0; i < (1 << 14); ++ i){
        if(get_count(i) == k)
            nums.push_back(i);
    }

    for(int i = 1; i <= n; ++ i){
        //g[i]是一个桶表示前i个数中有多少个数与x配对
        for(auto it : nums) ++ g[w[i] ^ it];
        //f[i]表示w1 ~ wi中有多少个数与wi+1配对
        f[i] = g[w[i + 1]];
    }
    for(int i = 0; i < m; ++ i){
        int l = read(), r = read();
        q[i] = {i, l, r};
    }
    block = sqrt(n);
    sort(q, q + m, cmp);

    for(int i = 0, L = 1, R = 0; i < m; ++ i){
        int l = q[i].l, r = q[i].r, id = q[i].id;
        if(R < r) range[L - 1].push_back({i, R + 1, r, -1});
        while(R < r) q[i].res += f[R ++ ];
        if(R > r) range[L - 1].push_back({i, r + 1, R, 1});
        while(R > r) q[i].res -= f[ -- R];//S(R) = f(R - 1)
        if(L < l) range[R].push_back({i, L, l - 1, -1});
        while(L < l) q[i].res += f[L - 1] + (k == 0), L ++ ;
        if(L > l) range[R].push_back({i, l, L - 1, 1});
        while(L > l) q[i].res -= f[L - 2] + (k == 0), L -- ;
    }
    memset(g, 0, sizeof g);
    for(int i = 1; i <= n; ++ i){
        for(auto it : nums) ++ g[w[i] ^it];
        for(auto it : range[i]){
            int id = it.id, l = it.l, r = it.r, t = it.t;
            for(int x = l; x <= r; ++ x)
                q[id].res += g[w[x]] * t;
        }
    }
    for(int i = 1; i < m; ++ i) q[i].res += q[i - 1].res;
    for(int i = 0; i < m; ++ i) ans[q[i].id] = q[i].res;
    for(int i = 0; i < m; ++ i) printf("%lld\n", ans[i]);
    return 0;
}

作者:繁凡さん
链接:https://www.acwing.com/solution/content/26293/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

树链剖分

AcWing 2568. 树链剖分

在这里插入图片描述

#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10, M = N << 1;

int n, m;
int h[N], w[N], e[M], ne[M], idx; //建树
int id[N], nw[N], cnt; //id:节点的dfn序编号,nw[id[i]]是i的权值w(w -> nw的映射)
int dep[N], sz[N], top[N], fa[N], son[N];
//sz:子树节点个数,top:重链的顶点,son:重儿子,fa:父节点
struct SegmentTree
{
    int l, r;
    LL sum, flag;
}tr[N << 2];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
//dfs1预处理
void dfs1(int u, int father, int depth)
{
    dep[u] = depth, fa[u] = father, sz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        dfs1(j, u, depth + 1);
        sz[u] += sz[j];
        if (sz[son[u]] < sz[j]) son[u] = j; //重儿子是子树节点最多的儿子
    }
}
//dfs2做剖分(t是重链的顶点)
void dfs2(int u, int t)
{
    id[u] = ++ cnt, nw[cnt] = w[u], top[u] = t;
    if (!son[u]) return; //叶节点结束
    dfs2(son[u], t); //重儿子重链剖分
    //处理轻儿子
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa[u] || j == son[u]) continue;
        dfs2(j, j); //轻儿子的重链顶点就是他自己
    }
}

//------------------------线段树的部分------------------------\\

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.flag)
    {
        left.sum += root.flag * (left.r - left.l + 1);
        left.flag += root.flag;
        right.sum += root.flag * (right.r - right.l + 1);
        right.flag += root.flag;
        root.flag = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, nw[r], 0};
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

void update(int u, int l, int r, int k)
{
    if (l <= tr[u].l && r >= tr[u].r)
    {
        tr[u].flag += k;
        tr[u].sum += k * (tr[u].r - tr[u].l + 1);
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) update(u << 1, l, r, k);
    if (r > mid) update(u << 1 | 1, l, r, k);
    pushup(u);
}

LL query(int u, int l, int r)
{
    if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL res = 0;
    if (l <= mid) res += query(u << 1, l, r);
    if (r > mid) res += query(u << 1 | 1, l, r);
    return res;
}

//------------------------线段树的部分------------------------\\

void update_path(int u, int v, int k)
{
    while (top[u] != top[v])    //向上爬找到相同重链
    {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        update(1, id[top[u]], id[u], k);    //dfs序原因,上面节点的id必然小于下面节点的id
        u = fa[top[u]];
    }
    if (dep[u] < dep[v]) swap(u, v);
    update(1, id[v], id[u], k); //在同一重链中,处理剩余区间
}
LL query_path(int u, int v)
{
    LL res = 0;
    while (top[u] != top[v])    //向上爬找到相同重链
    {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        res += query(1, id[top[u]], id[u]);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v]) swap(u, v);
    res += query(1, id[v], id[u]);  //在同一重链中,处理剩余区间
    return res;
}
void update_tree(int u, int k) //子树全部加上k
{
    update(1, id[u], id[u] + sz[u] - 1, k); //由于dfs序的原因,可以利用子树节点个数直接找到区间
}
LL query_tree(int u)
{
    return query(1, id[u], id[u] + sz[u] - 1); //原因同上
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    for (int i = 1; i <  n; i ++ )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        add(a, b), add(b, a);
    }
    dfs1(1, -1, 1);
    dfs2(1, 1);
    build(1, 1, n);
    scanf("%d", &m);
    while (m -- )
    {
        int t, u, v, k;
        scanf("%d%d", &t, &u);
        if (t == 1)
        {
            scanf("%d%d", &v, &k);
            update_path(u, v, k);
        }
        else if (t == 2)
        {
            scanf("%d", &k);
            update_tree(u, k);
        }
        else if (t == 3)
        {
            scanf("%d", &v);
            printf("%lld\n", query_path(u, v));
        }
        else printf("%lld\n", query_tree(u));
    }
    return 0;
}

作者:彩色铅笔
链接:https://www.acwing.com/solution/content/62664/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2524. 树链剖分II(换根树链剖分

在这里插入图片描述

#include <iostream>
#include <cstring>

using namespace std;

typedef long long LL;

const int N = 1e5 + 10, M = N << 1;

int n, m;
int h[N], e[M], ne[M], w[M], idx;
int id[N], nw[N], cnt;
int dep[N], sz[N], top[N], fa[N], son[N];
int root;
struct SegmentTree
{
    int l, r;
    LL sum, flag;
}tr[N << 2];

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void dfs1(int u, int father, int depth)
{
    dep[u] = depth, fa[u] = father, sz[u] = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == father) continue;
        dfs1(j, u, depth + 1);
        sz[u] += sz[j];
        if (sz[son[u]] < sz[j]) son[u] = j;
    }
}
void dfs2(int u, int t)
{
    id[u] = ++ cnt, nw[cnt] = w[u], top[u] = t;
    if (!son[u]) return;
    dfs2(son[u], t);
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa[u] || j == son[u]) continue;
        dfs2(j, j);
    }
}
//------------------------线段树的部分------------------------\\

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    auto &root = tr[u], &left = tr[u << 1], &right = tr[u << 1 | 1];
    if (root.flag)
    {
        left.sum += root.flag * (left.r - left.l + 1);
        left.flag += root.flag;
        right.sum += root.flag * (right.r - right.l + 1);
        right.flag += root.flag;
        root.flag = 0;
    }
}

void build(int u, int l, int r)
{
    tr[u] = {l, r, nw[r], 0};
    if (l == r) return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
    pushup(u);
}

void update(int u, int l, int r, int k)
{
    if (l <= tr[u].l && r >= tr[u].r)
    {
        tr[u].flag += k;
        tr[u].sum += k * (tr[u].r - tr[u].l + 1);
        return;
    }
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if (l <= mid) update(u << 1, l, r, k);
    if (r > mid) update(u << 1 | 1, l, r, k);
    pushup(u);
}

LL query(int u, int l, int r)
{
    if (l <= tr[u].l && r >= tr[u].r) return tr[u].sum;
    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    LL res = 0;
    if (l <= mid) res += query(u << 1, l, r);
    if (r > mid) res += query(u << 1 | 1, l, r);
    return res;
}
//------------------------线段树的部分------------------------\\

int lca(int u, int v)
{
    while (top[u] != top[v])
    {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        u = fa[top[u]];
    }
    if (dep[u] > dep[v]) swap(u, v);
    return u;
}
void update_path(int u, int v, int k)
{
    while (top[u] != top[v])
    {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        update(1, id[top[u]], id[u], k);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v]) swap(u, v);
    update(1, id[v], id[u], k);
}
LL query_path(int u, int v)
{
    LL res = 0;
    while (top[u] != top[v])
    {
        if (dep[top[u]] < dep[top[v]]) swap(u, v);
        res += query(1, id[top[u]], id[u]);
        u = fa[top[u]];
    }
    if (dep[u] < dep[v]) swap(u, v);
    res += query(1, id[v], id[u]);
    return res;
}
void update_tree(int u, int k)
{
    if (u == root) update(1, 1, n, k);
    else if (lca(u, root) == u)
    {
        update(1, 1, n, k);
        for (int i = h[u]; ~i; i = ne[i])
        {
            int j = e[i];
            if (lca(j, root) == j)
            {
                update(1, id[j], id[j] + sz[j] - 1, -k);
                break;
            }
        }
    }
    else update(1, id[u], id[u] + sz[u] - 1, k);
}
LL query_tree(int u)
{
    if (u == root) return query(1, 1, n);
    else if (lca(u, root) == u)
    {
        LL res = query(1, 1, n);
        for (int i = h[u]; ~i; i = ne[i])
        {
            int j = e[i];
            if (lca(j, root) == j)
            {
                res -= query(1, id[j], id[j] + sz[j] - 1);
                return res;
            }
        }
    }
    else return query(1, id[u], id[u] + sz[u] - 1);
}

int main()
{
    scanf("%d", &n);
    memset(h, -1, sizeof h);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    for (int i = 2; i <= n; i ++ )
    {
        int a;
        scanf("%d", &a);
        add(a, i);
    }
    dfs1(1, -1, 1);
    dfs2(1, 1);
    build(1, 1, n);
    scanf("%d", &m);
    while (m -- )
    {
        int t, u, v, k;
        scanf("%d%d", &t, &u);
        if (t == 1) root = u;
        else if (t == 2)
        {
            scanf("%d%d", &v, &k);
            update_path(u, v, k);
        }
        else if (t == 3)
        {
            scanf("%d", &k);
            update_tree(u, k);
        }
        else if (t == 4)
        {
            scanf("%d", &v);
            printf("%lld\n", query_path(u, v));
        }
        else printf("%lld\n", query_tree(u));
    }
    return 0;
}

AcWing 918. 软件包管理器

在这里插入图片描述
在这里插入图片描述
轻重链剖分
不难看出这个这个依赖关系是一棵以 00 号点为根的树

每次对编号为 xx 的节点操作
安装操作需要把根节点到 xx 路径上的点全部置为 11
卸载操作需要把以 xx 为根的子树中的点全部置为 00
好像没什么值的分析的
每次操作需要求一下状态被改变的软件包数量,所以在线段树中维护一下已安装的软件包数量之和。
区间赋值也懒标记pushdown就行。
详见代码。

时间复杂度 O(mlog2n)

#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

const int N = 100010, M = N << 1;

int h[N], e[N], ne[N], idx;
void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++;
}

int n, m;

int dep[N], f[N], sz[N], son[N];
void dfs(int u, int fa)
{
    dep[u] = dep[fa] + 1, f[u] = fa, sz[u] = 1;
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        dfs(j, u);
        sz[u] += sz[j];
        if(sz[j] > sz[son[u]])  son[u] = j;
    }
}

int id[N], rk[N], top[N], tot;
void dfs2(int u, int t)
{
    id[u] = ++ tot, rk[tot] = u, top[u] = t;
    if(son[u])  dfs2(son[u], t);
    for(int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if(son[u] == j)  continue;
        dfs2(j, j);
    }
}

struct Node{
    int l, r;
    int sum, lazy;
}tr[N << 2];

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r)  return;
    int mid = l + r >> 1;
    build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
}

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void pushdown(int u)
{
    if(tr[u].lazy)
    {
        Node &left = tr[u << 1], &right = tr[u << 1 | 1];
        if(tr[u].lazy == 1)
        {
            left.sum = left.r - left.l + 1;
            right.sum = right.r - right.l + 1;
        }
        else
            left.sum = right.sum = 0;
        left.lazy = right.lazy = tr[u].lazy;
        tr[u].lazy = 0;
    }
}

//  type为1区间覆盖,type为2区间删除
void modify(int u, int l, int r, int type)
{
    if(tr[u].l >= l and tr[u].r <= r)
    {
        if(type == 1)
        {
            tr[u].sum = tr[u].r - tr[u].l + 1;
            tr[u].lazy = 1;
        }
        else
        {
            tr[u].sum = 0;
            tr[u].lazy = 2;
        }
        return;
    }

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1;
    if(l <= mid)  modify(u << 1, l, r, type);
    if(r > mid)  modify(u << 1 | 1, l, r, type);
    pushup(u);
}

int query(int u, int l, int r)
{
    if(tr[u].l >= l and tr[u].r <= r)  return tr[u].sum;

    pushdown(u);
    int mid = tr[u].l + tr[u].r >> 1, res = 0;
    if(l <= mid)  res = query(u << 1, l, r);
    if(r > mid)  res += query(u << 1 | 1, l, r);
    return res;
}

//  把根节点到x路径上的点全部置为1
int install(int x)
{
    int res = 0;
    while(x)
    {
        res += (id[x] - id[top[x]] + 1) - query(1, id[top[x]], id[x]);
        modify(1, id[top[x]], id[x], 1);
        x = f[top[x]];
    }
    return res;
}

//  把以x为根的子树中的点全部置为0
int uninstall(int x)
{
    int res = query(1, id[x], id[x] + sz[x] - 1);
    modify(1, id[x], id[x] + sz[x] - 1, 2);
    x = f[top[x]];
    return res;
}

int main()
{
    memset(h, -1, sizeof h);

    scanf("%d", &n);
    for(int i = 2; i <= n; i ++)
    {
        int x;
        scanf("%d", &x);
        x ++;
        add(x, i);
    }

    dfs(1, 0);
    dfs2(1, 1);
    build(1, 1, n);

    scanf("%d", &m);
    while(m --)
    {
        char op[10];
        int x;
        scanf("%s%d", op, &x);
        x ++;
        if(op[0] == 'i')  printf("%d\n", install(x));
        else  printf("%d\n", uninstall(x));
    }
    return 0;
}

作者:滑稽_ωノ
链接:https://www.acwing.com/solution/content/21661/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

动态树

Link - Cut Tree
LCT 是用于解决 动态树问题 的数据结构

动态树问题:

维护一个森林,支持删除某条边,加入某条边,并保证加边,删边后仍是!!森林!!。我们要维护该森林的一些信息。
一般的操作有两点连通性, 两点路径权值和, 连接两点 和 切断某条边、修改信息 等
他和 树剖 有几分相似,树剖 是通过按 子树大小 进行剖分,然后动态维护剖分出的 lognlog⁡n 个区间

LCT 则是用多个 Splay 来维护 多个实链,Splay 的特性就使得我们可以进行 树的合并、分割 操作

LCT 的好处在于:

树链剖分 能做的 LCT 基本都能做
树链剖分 处理一次询问的时间复杂度为 O(log2n)O(log2⁡n),而 LCTLCT 是 O(logn)O(log⁡n)
不过需要注意,LCTLCT 的常数很大(从他的复杂性也能看出)
因此板子里可以适当增加 宏定义 和 内敛函数 (我的常数很大,评测姬你忍一下)

实链剖分
对于一个点连向它所有儿子的边 , 我们自己选择 一条边 进行剖分,我们称被选择的边为 实边,其他边则为 虚边。
对于 实边,我们称它所连接的儿子为 实儿子。
对于一条由 实边 组成的 链,我们同样称之为 实链。
对于 每条 实链,我们 分别 建一个 Splay 来维护整个 链区间 的信息。
正是因为 实链 我们可以自己任意选择,使得 实链剖分 可以适用于动态树的问题
惯例贴出 y总的抽象图示

辅助树splay
辅助树 由多棵 Splay 组成,每棵 Splay 维护原树中的 一条路径,且 中序遍历 这棵 Splay 得到的点序列,从前到后对应原树“从上到下”的一条路径

原树 每个节点与 辅助树 的 Splay 节点一一对应

辅助树 的各棵 Splay 之间并不是独立的。每棵 Splay 的根节点的父亲节点本应是空,但在 LCT 中每棵 Splay 的根节点的父亲节点指向原树中 这条链 的父亲节点(即链最顶端的点的父亲节点)。这类父亲链接与通常 Splay 的父亲链接区别在于儿子认父亲,而父亲不认儿子,对应原树的一条 虚边。因此,每个连通块恰好有一个点的父亲节点为空。

具体图示如下(来源:OI-WiKi)

原树:

对应辅助树:

辅助树和原树的关系
原树 中的 实链 在 辅助树 中都在同一颗 Splay 里
原树 中的 虚链 : 在 辅助树 中,子节点 所在 Splay 的 Father 指向 父节点,但是 父节点 的 两个儿子 都不指向 子节点。
原树的 Father 指向不等于 辅助树的 Father 指向。
辅助树 是可以在满足 辅助树、Splay 的性质下任意换根的。
虚实链变换 可以轻松在 辅助树 上完成,这也就是实现了 动态维护树链剖分。
以上就是对 LCT 的大致介绍,接下来只需实现 亿点点 功能函数,就完成LCT啦

Splay 系函数的变化
pushup(x):本题需要维护的是路径的异或和
void pushup(int x)
{
    tr[x].sum = tr[tr[x].s[0]].sum ^ tr[x].v ^ tr[tr[x].s[1]].sum;
}
pushdown(x):不同于翻转区间那题,这里懒标记维护的是 修改后 的懒标记
void pushdown(int x)
{
    if (tr[x].rev)
    {
        pushrev(tr[x].s[0]), pushrev(tr[x].s[1]);
        tr[x].rev = 0;
    }
}
rotate(int x):在修改 z 与 y 这条边的时候特判 y 是否为根节点
在前面已经介绍过了,原本 splay 的根节点应该是没有父节点的,但 LCT 里我们让这个空指针来维护虚边

isroot(x) 函数后面会介绍

void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    if (!isroot(y)) tr[z].s[tr[z].s[1] == y] = x; //唯一不同的地方
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}
splay(int x):把节点 x 转到辅助树 splay 的根节点
我先说一下这里不同的地方,以往 splay 找某个节点的时候,都是从根节点往下找(按照 BST 的性质)

但是在 LCT 中,splay 充当的是辅助树的角色,我们获得 splay 中的节点是通过原树中对应节点的编号

换而言之,我们是直接获得 splay 中的某个节点,而不是自上而下递归找到的

所以,在做 splay 转到根节点的旋转操作时,我们需要先 自上而下 把 懒标记 下传

这就是与传统 splay 相矛盾的地方

这里有两种写法,一个是递归(码量少,但是比较看评测姬心情),一个是迭代(y总的栈写法)
//递归写法
void update(int x)
{
    if (!isroot(x)) update(tr[x].p);
    pushdown(x);
}
void splay(int x)
{
    update(x);
    while (!isroot(x))
    {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y))
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
}

//----------分割线-------------

//迭代写法
void splay(int x)
{
    int top = 0, r = x;
    stk[ ++ top] = r;
    while (!isroot(r)) stk[ ++ top] = r = tr[r].p;
    while (top) pushdown(stk[top -- ]);
    while (!isroot(x))
    {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y))
            if ((tr[y].s[1] == x) ^ (tr[z].s[1] == y)) rotate(x);
            else rotate(y);
        rotate(x);
    }
}
新操作
access(x) :建立一条从根节点到 x 的实链(同时将 x 变成对应 splay 的根节点)
把当前节点转到根。
把儿子换成之前的节点。
更新当前点的信息。
把当前点换成当前点的父亲,继续操作。
void access(int x)  //建立一条从根节点到 x 的实链(同时将 x 变成对应 splay 的根节点)
{
    int z = x;  //记录初始的节点编号
    for (int y = 0; x; y = x, x = tr[x].p) //x沿着虚边往上找根
    {
        splay(x);   //先转到当前辅助树的根
        tr[x].s[1] = y, pushup(x);  //把上个树接到中序遍历后面
    }
    splay(z);   //把初始的节点转到根
}
makeroot(x) 将 x 变成原树的根节点
access(x) 操作之后,x 会被旋转到splay的树根,此时我们只需反转 x,就可以达到反转 splay 中序遍历的效果

而 splay 中序遍历被反转,也就意味着原树中,从根节点到 x 的路径被反转,从而实现把 x 变成根的操作

void makeroot(int x) //将 x 变成原树的根节点(且左子树为空)
{
    access(x);  //此时x为辅助树的根节点,直接反转中序遍历即可
    pushrev(x);
}
findroot(x) :找到 x 所在的原树的根节点,再将原树的根节点旋转到辅助树的根节点
先 access(x) 打通从根节点到 x 的实链(此时 x 在 splay 的根节点),然后找到该 splay 中序遍历的第一个节点

int findroot(int x) //找到 x 所在的原树的根节点,再将原树的根节点旋转到辅助树的根节点
{
    access(x);  //打通根节点到 x 的实链,当前 x 位于辅助树的根节点位置
    while (tr[x].s[0]) pushdown(x), x = tr[x].s[0];  //找到辅助树中序遍历的第一个元素(左下角)
    splay(x);   //转到根节点
    return x;
}
split(x, y) :将 x 到 y 的路径变为实边路径
比较简单,先把 x 放到根,再打通从 根 到 y 的路径即可

void split(int x, int y)    //将 x 到 y 的路径变为实边路径
{
    makeroot(x);    //先把 x 设为根
    access(y);      //在打通根到 y 的实链即可
}
link(x, y) :若 x , y 不连通,则加入 (x, y) 这条边
先把 x 放到根,查找一下 y 所在树的根节点是不是 x。

如果不是(查找根节点会把 y 转到他辅助树的根节点)则加边

void link(int x, int y) //若 x , y 不连通,则加入 (x, y) 这条边
{
    makeroot(x);    //先把 x 设为根
    if (findroot(y) != x) tr[x].p = y;  //如果不连通,则把 x 的实链接到 y 上即可
}
cut(x, y) :若边 (x, y) 存在,则删掉(x, y)这条边
先把 x 放到根,判断此时:

y 所在的原树中的根是否是 x
y 的父节点是否是根 x
y 是否有左孩子(中序遍历紧挨在 x 的后面)
满足上述三条,说明 边 (x,y) 存在,cut 掉

void cut(int x, int y)  //若边 (x, y) 存在,则删掉(x, y)这条边
{
    makeroot(x);
    if (findroot(y) == x && tr[x].s[1] == y && !tr[y].s[0])
    {
        tr[x].s[1] = tr[y].p = 0;
        pushup(x);
    }
}
isroot(x) :判断 x 是否是所在辅助树 splay 的根节点
这个比较简单,按照我们之前所说的,他有父亲,但他父亲不认他 泪目

bool isroot(int u)  //判断 u 是否为实链的顶部
{
    return tr[tr[u].p].s[0] != u && tr[tr[u].p].s[1] != u;
}
一些提醒(From OI-Wiki)
干点啥前一定要想一想需不需要 PushUp 或者 PushDown, LCT 由于特别灵活的原因,少 Pushdown 或者 Pushup 一次就可能把修改改到不该改的点上!
LCT 的 Rotate 和 Splay 的不太一样,if (z) 一定要放在前面。
LCT 的 Splay 操作就是旋转到根,没有旋转到谁儿子的操作,因为不需要。

AcWing 2539. 动态树

在这里插入图片描述

Code
以下为本题完整代码:

#include <iostream>

using namespace std;

const int N = 1e5 + 10;

int n, m;
struct Splay
{
    int s[2], p, v;
    int sum, rev;
}tr[N];

bool isroot(int u)  //判断 u 是否为实链的顶部
{
    return tr[tr[u].p].s[0] != u && tr[tr[u].p].s[1] != u;
}
//------------------Splay系函数------------------\\

void pushup(int u)
{
    tr[u].sum = tr[tr[u].s[0]].sum ^ tr[u].v ^ tr[tr[u].s[1]].sum;
}
void pushrev(int u)
{
    swap(tr[u].s[0], tr[u].s[1]);
    tr[u].rev ^= 1;
}
void pushdown(int u)
{
    if (tr[u].rev)
    {
        pushrev(tr[u].s[0]);
        pushrev(tr[u].s[1]);
        tr[u].rev = 0;
    }
}
void rotate(int x)
{
    int y = tr[x].p, z = tr[y].p;
    int k = tr[y].s[1] == x;
    if (!isroot(y)) tr[z].s[tr[z].s[1] == y] = x; //唯一的不同处
    tr[x].p = z;
    tr[y].s[k] = tr[x].s[k ^ 1], tr[tr[x].s[k ^ 1]].p = y;
    tr[x].s[k ^ 1] = y, tr[y].p = x;
    pushup(y), pushup(x);
}
void splay(int x)   //迭代写法
{
    static int stk[N];  //先自上而下下传懒标记
    int tt = 0, t = x;
    stk[ ++ tt] = t;
    while (!isroot(t)) stk[ ++ tt] = t = tr[t].p;
    while (tt) pushdown(stk[tt -- ]);
    //接下来基本与splay板子相同
    while (!isroot(x))
    {
        int y = tr[x].p, z = tr[y].p;
        if (!isroot(y))
            if ((tr[z].s[1] == y) ^ (tr[y].s[1] == x)) rotate(x);
            else rotate(y);
        rotate(x);
    }
}
//------------------Splay系函数------------------\\

void access(int x)  //建立一条从根节点到 x 的实链(同时将 x 变成对应 splay 的根节点)
{
    int z = x;  //记录初始的节点编号
    for (int y = 0; x; y = x, x = tr[x].p) //x沿着虚边往上找根
    {
        splay(x);   //先转到当前辅助树的根
        tr[x].s[1] = y, pushup(x);  //把上个树接到中序遍历后面
    }
    splay(z);   //把初始的节点转到根
}
void makeroot(int x) //将 x 变成原树的根节点(且左子树为空)
{
    access(x);  //此时x为辅助树的根节点,直接反转中序遍历即可
    pushrev(x);
}
int findroot(int x) //找到 x 所在的原树的根节点,再将原树的根节点旋转到辅助树的根节点
{
    access(x);  //打通根节点到 x 的实链,当前 x 位于辅助树的根节点位置
    while (tr[x].s[0]) pushdown(x), x = tr[x].s[0];  //找到辅助树中序遍历的第一个元素(左下角)
    splay(x);   //转到根节点
    return x;
}
void split(int x, int y)    //将 x 到 y 的路径变为实边路径
{
    makeroot(x);    //先把 x 设为根
    access(y);      //在打通根到 y 的实链即可
}
void link(int x, int y) //若 x , y 不连通,则加入 (x, y) 这条边
{
    makeroot(x);    //先把 x 设为根
    if (findroot(y) != x) tr[x].p = y;  //如果不连通,则把 x 的实链接到 y 上即可
}
void cut(int x, int y)  //若边 (x, y) 存在,则删掉(x, y)这条边
{
    makeroot(x);
    if (findroot(y) == x && tr[x].s[1] == y && !tr[y].s[0])
    {
        tr[y].p = tr[x].s[1] = 0;
        pushup(x);
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &tr[i].v);
    while (m -- )
    {
        int t, x, y;
        scanf("%d%d%d", &t, &x, &y);
        if (t == 0)
        {
            split(x, y);
            printf("%d\n", tr[y].sum);
        }
        else if (t == 1) link(x, y);
        else if (t == 2) cut(x, y);
        else
        {
            splay(x);
            tr[x].v = y;
            pushup(x);
        }
    }
    return 0;
}

作者:彩色铅笔
链接:https://www.acwing.com/solution/content/63747/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

AcWing 999. 魔法森林

在这里插入图片描述
在这里插入图片描述
前置芝士 LCT + 并查集
题目描述
给定n个点和m条边.

每条边包含x, y, a, b, 表示起点、终点、a精灵的最低限制、b精灵最低限制.
只有在携带大于等于限制数量的精灵才能通过该条路.

现在求从1到n最少需要携带的精灵数量(即a精灵和b精灵数量).

题目思路
由于需要a, b都满足才能通过某条边, 所以结果必定为某条边的aiai或bibi, 因此可以固定a的信息来降低复杂度.
即每次选取小于等于aiai大小的边去维护一条1到n的路径.
因此先将边按照a的数量排序, 那么此时只需要维护1到n的路径中的最大值b即可.

设bmxbmx为当前1到n路径上的所需要的b精灵的最大值, aiai表示第i条边的a精灵所需要的数量.
此时按a从小到大逐渐加入边, 如果加入该边后1和n连通了, 那么代表1到n的所需要带的精灵就是ai+bmxai+bmx.
那么最终答案就是该过程中的最小值.

动态加边加维护最大值! 直接就是LCT. 你是不是数据结构学傻了.jpg

虽然题目给出的是一张图, 但实际上只需要维护出一条从1到n的路径即可.
因此当新加入一条边会使维护的树变成图时, 就需要去找到环, 若新边比环中最大值小, 那么将环中的最大边删去, 加入新边即可.
为啥一定是删最长边呢? 因为每条边加入的答案为ai+bmxai+bmx, 因此删除最大边, 加入一个小于最大边的边, 答案必定不会增大.

注意: 本题findroot会被卡常, 所以上并查集
小技巧: 边权转点权, 可以通过在两点之间加入一个新的点将权值赋给该点.


/*
 * 思路:
 * 将所有边按照a来进行升序排序,依次从小到大将边加入,在已经加入边的图上找1到n间路径中b的最大值,求出的b最大值和当前枚举的a的和用于更新全局的最小值答案
 * 原始点id从1到n, 权值是0,将边抽象成点,如果有一条边
 * (A, B), 权值为W,边id是i, 将其抽象为一个权值是W的点, 添加边(A, B), 就是在图上添加(A, i+(n+1))和(B, i+(n+1)) 这两条边,这样问题就转换为
 * 动态求节点1到节点n之间路径上b的点权值的最大值,当添加一条边(A,B)时候,如果发现AB已经连通,再添加(A,B)会形成环,此时找到图上A,B之间路径上最大
 * 的点权值max_w(对应原来图里面边的最大权值),如果max_w > W, 则可以将这条最大的边删掉,然后添加(A,B), 这样不会影响连通性,也不会造成漏解,如果max_w <= W
 * 那(A,B)这条边没有添加的必要,因为不可能因为添加这条边得到更优解,用这样的策略可以一直保持图是没有环的,形状始终是森林,这样就可以用动态树来维护
 * 这个图,利用动态树的特性快速求路径上的最大点权值
 *
 */

#include <algorithm>
using namespace std;

// 最大节点个数,节点从1开始编号
const int MAX_NODE_NUM = 200005;

// Splay节点
struct Node {
    int child[2];           // 两个子节点id
    int parent;             // 父节点id
    int val;                // 节点的数值
    int max_val;            // 区间上的最大值
    int max_node_idx;       // 数值最大的点的编号
    int rev_flag;           // 区间翻转标记
} __node_pool[MAX_NODE_NUM];
int stack[MAX_NODE_NUM];


// 最大的边数量
const int MAX_EDGE_NUM = 100005;

// 边结构体
struct edge_node {
    int node1, node2, a, b;

    bool operator < (const edge_node& other) const {
        return a < other.a;
    }

} __edge_nodes[MAX_EDGE_NUM];


int n, m;

// 并查集用的数组
int pp[MAX_NODE_NUM];


// 当前节点的左右子树做翻转操作
void __process_rev(int idx) {
    Node& node = __node_pool[idx];
    int t = node.child[0];
    node.child[0] = node.child[1];
    node.child[1] = t;
}


// 归并两颗子树的结果
void __push_up(int idx) {
    Node& node = __node_pool[idx];
    int ans = node.val;
    int ans_idx = idx;
    if (node.child[0] != 0) {
        if (__node_pool[node.child[0]].max_val > ans) {
            ans_idx = __node_pool[node.child[0]].max_node_idx;
            ans = __node_pool[node.child[0]].max_val;
        }
    }

    if (node.child[1] != 0) {
        if (__node_pool[node.child[1]].max_val > ans) {
            ans_idx = __node_pool[node.child[1]].max_node_idx;
            ans = __node_pool[node.child[1]].max_val;
        }
    }

    node.max_val = ans;
    node.max_node_idx = ans_idx;
}


// 下传区间翻转lazy标记
void __push_down_rev_flag(int idx) {
    Node& node = __node_pool[idx];
    if (node.rev_flag == 0) {
        return;
    }

    if (node.child[0] != 0) {
        __node_pool[node.child[0]].rev_flag ^= 1;
    }

    if (node.child[1] != 0) {
        __node_pool[node.child[1]].rev_flag ^= 1;
    }

}

// 区间反转的push_down操作
void __push_down_rev(int idx) {
    Node& node = __node_pool[idx];

    if (node.rev_flag == 0) {
        return;
    }

    __push_down_rev_flag(idx);
    if (node.child[0] != 0) {
        __process_rev(node.child[0]);
    }

    if (node.child[1] != 0) {
        __process_rev(node.child[1]);
    }

    node.rev_flag = 0;
}


// 统一的push_down函数
void __push_down(int idx) {
    __push_down_rev(idx);
}


// 判断节点是否是其所在的splay的根节点
bool is_splay_root(int idx) {
    int parent = __node_pool[idx].parent;
    return idx != __node_pool[parent].child[0] && idx != __node_pool[parent].child[1];
}


// 修改lazy标记的数值
void reverse_flag(int idx) {
    Node& node = __node_pool[idx];
    node.rev_flag ^= 1;
    __process_rev(idx);           // 修改了lazy标记,一定要让标记的影响在当前节点上作用一次
}

// 统一的左旋和右旋函数,将编号为x对应的节点向上一层旋转
void __rotate(int x) {
    int y = __node_pool[x].parent, z = __node_pool[y].parent;
    int k1 = __node_pool[y].child[1] == x, k2 = __node_pool[z].child[1] == y;

    // 如果y是splay的根,那z的子节点指针表示的是实边信息,不能做修改z的子节点信息
    if (!is_splay_root(y)) {
        __node_pool[z].child[k2] = x;
    }

    __node_pool[x].parent = z;
    __node_pool[y].child[k1] = __node_pool[x].child[k1^1];
    __node_pool[__node_pool[x].child[k1^1]].parent = y;
    __node_pool[x].child[k1^1] = y;
    __node_pool[y].parent = x;

    __push_up(y);
    __push_up(x);
}

// 将x所在实边路径对应的Splay上的lazy标记都使用掉,然后将x旋转到Splay的根
void splay(int x) {
    int top = 0;
    int r = x;

    stack[++top] = x;
    while (!is_splay_root(r)) {
        r = __node_pool[r].parent;
        stack[++top] = r;
    }

    // 从上到下push_down一次
    while (top) {
        __push_down(stack[top--]);
    }

    // 将x旋转到所属的splay的根上
    while (!is_splay_root(x)) {
        int y = __node_pool[x].parent;
        int z = __node_pool[y].parent;

        if (!is_splay_root(y)) {
            if ((__node_pool[y].child[1] == x) ^ (__node_pool[z].child[1] == y)) {
                __rotate(x);
            } else {
                __rotate(y);
            }
        }

        __rotate(x);
    }
}

// 将节点x所属于的动态树的根r和x之间的路径变为实边路径, 同时将x变为其最后所属的splay的根
void access(int x) {
    int z = x;
    for (int y = 0; x != 0; y = x, x = __node_pool[x].parent) {
        splay(x);
        __node_pool[x].child[1] = y;
        __push_up(x);
    }

    splay(z);
}

// 将x变为整个动态树的根
void make_root(int x) {
    access(x);
    reverse_flag(x);
}

// 找到x所属的动态树的根节点id, 然后将这个节点旋转到其所属的Splay的根位置
int find_root(int x) {
    access(x);
    while (__node_pool[x].child[0] != 0) {
        __push_down(x);
        x = __node_pool[x].child[0];
    }

    splay(x);
    return x;
}

// 将两个节点x, y之间的路径变为实边路径, 且y变为其所属的splay的根
void split(int x, int y) {
    make_root(x);
    access(y);
}

// 如果x和y不在一颗动态树上,添加一条从x到y的虚边
void link(int x, int y) {
    make_root(x);
    if (find_root(y) != x) {
        __node_pool[x].parent = y;
    }
}


// 如果xy之前在动态树上存在边相连,将该边断掉
void cut(int x, int y) {
    make_root(x);
    if (find_root(y) == x && __node_pool[y].parent == x && __node_pool[y].child[0] == 0) {
        __node_pool[x].child[1] = __node_pool[y].parent = 0;
        __push_up(x);
    }
}


int get_root(int node) {
    if (pp[node] == node) {
        return node;
    }

    pp[node] = get_root(pp[node]);
    return pp[node];
}

void merge(int a, int b) {
    int root1 = get_root(a), root2 = get_root(b);
    if (root1 != root2) {
        pp[root1] = root2;
    }
}

bool in_same_set(int a, int b) {
    return get_root(a) == get_root(b);
}


// 获取动态树上从node1到node2路径上权值最大的点的编号
int get_max_dis_node_idx(int node1, int node2) {
    split(node1, node2);
    return __node_pool[node2].max_node_idx;
}

// 获取动态树上从node1到node2路径上最大的点权值
int get_max_dis(int node1, int node2) {
    split(node1, node2);
    return __node_pool[node2].max_val;
}

// 添加一条边
void add_edge(int node1, int node2, int edge_node, int w) {
    if (in_same_set(node1, node2)) {
        int max_node_idx = get_max_dis_node_idx(node1, node2);
        int max_weight = __node_pool[max_node_idx].val;

        if (max_weight <= w) {
            // 当前那这条边没有加的必要,不会让答案更优
            return;
        }

        cut(max_node_idx, __edge_nodes[max_node_idx - (n+1)].node1);
        cut(max_node_idx, __edge_nodes[max_node_idx - (n+1)].node1);
    }

    link(edge_node, node1);
    link(edge_node, node2);
}


int main() {
    //freopen("/Users/grh/Programming/CLionProjects/SimpleTest/input.txt", "r", stdin);
    int node1, node2, a, b;
    scanf("%d %d", &n, &m);
    for (int i = 0; i < m; i++) {
        scanf("%d %d %d %d", &(__edge_nodes[i].node1), &(__edge_nodes[i].node2), &(__edge_nodes[i].a), &(__edge_nodes[i].b));
    }

    // 按照a权值进行排序
    sort(__edge_nodes, __edge_nodes + m);

    // 一开始图上已经存在的原始的点权值全部都是0, 编号1到n
    for (int i = 1; i <= n; i++) {
        __node_pool[i].val = 0;
        __node_pool[i].max_val = 0;
        __node_pool[i].max_node_idx = i;
        pp[i] = i;
    }

    // 边抽象出来的点的编号n+1 到 n+m
    for (int i = 0; i < m; i++) {
        __node_pool[i+n+1].val = __edge_nodes[i].b;
        __node_pool[i+n+1].max_val = __edge_nodes[i].b;
        __node_pool[i+n+1].max_node_idx = i+n+1;
        pp[i+n+1] = i+n+1;
    }

    int ans = 0x7fffffff;

    int i = 0;
    while (i < m) {
        int j = i;
        int wei_a = __edge_nodes[i].a;
        while (j < m && __edge_nodes[j].a == wei_a) {
            if (__edge_nodes[j].node1 != __edge_nodes[j].node2) {
                add_edge(__edge_nodes[j].node1, __edge_nodes[j].node2, j+(n+1), __edge_nodes[j].b);
                merge(__edge_nodes[j].node1, __edge_nodes[j].node2);
            }

            j += 1;
        }

        if (in_same_set(1, n)) {
            ans = min(ans, wei_a + get_max_dis(1, n));
        }
        i = j;
    }

    if (ans != 0x7fffffff) {
        printf("%d\n", ans);
    } else {
        printf("-1\n");
    }

    return 0;
}


作者:皓首不倦
链接:https://www.acwing.com/solution/content/48969/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

DLX之精确覆盖问题

AcWing 1067. 精确覆盖问题

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5510;

int n, m;
int l[N], r[N], u[N], d[N], s[N], row[N], col[N], idx;
int ans[N], top;

void init()
{
    for (int i = 0; i <= m; i ++ )
    {
        l[i] = i - 1, r[i] = i + 1;
        u[i] = d[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()
{
    scanf("%d%d", &n, &m);
    init();
    for (int i = 1; i <= n; i ++ )
    {
        int hh = idx, tt = idx;
        for (int j = 1; j <= m; j ++ )
        {
            int x;
            scanf("%d", &x);
            if (x) add(hh, tt, i, j);
        }
    }

    if (dfs())
    {
        for (int i = 1; i <= top; i ++ ) printf("%d ", ans[i]);
        puts("");
    }
    else puts("No Solution!");

    return 0;
}

AcWing 169. 数独

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 20000;

int m = 16 * 16 * 4;
int u[N], d[N], l[N], r[N], s[N], col[N], row[N], idx;
int 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;
}

AcWing 956. 智慧珠游戏

DLX之重复覆盖问题

AcWing 2713. 重复覆盖问题

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010;

int n, m;
int l[N], r[N], u[N], d[N], col[N], row[N], s[N], idx;
int ans[N];
bool st[110];

void init()
{
    for (int i = 0; i <= m; i ++ )
    {
        l[i] = i - 1, r[i] = i + 1;
        col[i] = u[i] = d[i] = i;
        s[i] = 0;
    }
    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 ++ ;
}

int h()
{
    int cnt = 0;
    memset(st, 0, sizeof st);
    for (int i = r[0]; i; i = r[i])
    {
        if (st[col[i]]) continue;
        cnt ++ ;
        st[col[i]] = true;
        for (int j = d[i]; j != i; j = d[j])
            for (int k = r[j]; k != j; k = r[k])
                st[col[k]] = true;
    }
    return cnt;
}

void remove(int p)
{
    for (int i = d[p]; i != p; i = d[i])
    {
        r[l[i]] = r[i];
        l[r[i]] = l[i];
    }
}

void resume(int p)
{
    for (int i = u[p]; i != p; i = u[i])
    {
        r[l[i]] = i;
        l[r[i]] = i;
    }
}

bool dfs(int k, int depth)
{
    if (k + h() > depth) return false;
    if (!r[0]) return true;
    int p = r[0];
    for (int i = r[0]; i; i = r[i])
        if (s[p] > s[i])
            p = i;

    for (int i = d[p]; i != p; i = d[i])
    {
        ans[k] = row[i];
        remove(i);
        for (int j = r[i]; j != i; j = r[j]) remove(j);
        if (dfs(k + 1, depth)) return true;
        for (int j = l[i]; j != i; j = l[j]) resume(j);
        resume(i);
    }
    return false;
}

int main()
{
    scanf("%d%d", &n, &m);
    init();
    for (int i = 1; i <= n; i ++ )
    {
        int hh = idx, tt = idx;
        for (int j = 1; j <= m; j ++ )
        {
            int x;
            scanf("%d", &x);
            if (x) add(hh, tt, i, j);
        }
    }

    int depth = 0;
    while (!dfs(0, depth)) depth ++ ;
    printf("%d\n", depth);
    for (int i = 0; i < depth; i ++ ) printf("%d ", ans[i]);
    return 0;
}

AcWing 182. 破坏正方形

AcWing 2724. 雷达

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<double, double> PDD;
const int N = 55, M = N * N;

int n, m, k;
int l[M], r[M], u[M], d[M], row[M], col[M], s[M], idx;
bool st[N];
PDD city[N], station[N];

void init()
{
    for (int i = 0; i <= n; i ++ )
    {
        l[i] = i - 1, r[i] = i + 1;
        col[i] = u[i] = d[i] = i;
        s[i] = 0;
    }
    l[0] = n, r[n] = 0;
    idx = n + 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 ++ ;
}

int h()
{
    int res = 0;
    memset(st, 0, sizeof st);
    for (int i = r[0]; i; i = r[i])
    {
        if (st[i]) continue;
        res ++ ;
        st[i] = true;
        for (int j = d[i]; j != i; j = d[j])
            for (int k = r[j]; k != j; k = r[k])
                st[col[k]] = true;
    }
    return res;
}

void remove(int p)
{
    for (int i = d[p]; i != p; i = d[i])
    {
        r[l[i]] = r[i];
        l[r[i]] = l[i];
    }
}

void resume(int p)
{
    for (int i = u[p]; i != p; i = u[i])
    {
        r[l[i]] = i;
        l[r[i]] = i;
    }
}

bool dfs(int k, int depth)
{
    if (k + h() > depth) return false;
    if (!r[0]) return true;
    int p = r[0];
    for (int i = r[0]; i; i = r[i])
        if (s[p] > s[i])
            p = i;

    for (int i = d[p]; i != p; i = d[i])
    {
        remove(i);
        for (int j = r[i]; j != i; j = r[j]) remove(j);
        if (dfs(k + 1, depth)) return true;
        for (int j = l[i]; j != i; j = l[j]) resume(j);
        resume(i);
    }

    return false;
}

bool check(PDD a, PDD b, double mid)
{
    double dx = a.x - b.x;
    double dy = a.y - b.y;
    return dx * dx + dy * dy <= mid * mid;
}

bool check(double mid)
{
    init();
    for (int i = 1; i <= m; i ++ )
    {
        int hh = idx, tt = idx;
        for (int j = 1; j <= n; j ++ )
            if (check(station[i], city[j], mid))
                add(hh, tt, i, j);
    }

    int depth = 0;
    while (depth <= k && !dfs(0, depth)) depth ++ ;
    return depth <= k;
}

int main()
{
    int T;
    scanf("%d", &T);
    while (T -- )
    {
        scanf("%d%d%d", &n, &m, &k);
        for (int i = 1; i <= n; i ++ ) scanf("%lf%lf", &city[i].x, &city[i].y);
        for (int i = 1; i <= m; i ++ ) scanf("%lf%lf", &station[i].x, &station[i].y);
        double l = 0, r = 2000;
        while (r - l > 1e-10)
        {
            double mid = (l + r) / 2;
            if (check(mid)) r = mid;
            else l = mid;
        }
        printf("%lf\n", r);
    }
    return 0;
}

左偏树

AcWing 2714. 左偏树

在这里插入图片描述


/*
左偏树构成的森林实现
        支持如下操作:

1. 用一个数值构建一个新的左偏树堆 make_new_heap(val), 返回新建节点的id号
2. 合并id1元素所在的左偏树和id2元素所在的左偏树 merge(id1, id2),返回新的左偏树的根的id号
3. 将新数值val放入编号为id的数值存在的左偏树 put(id, val),返回数值是val的新节点的id号
4. 查询id号元素所在堆的堆顶元素的数值 get_top_val(id), 返回堆顶元素(id, val)二元组
5. 将id号所在的堆的堆顶元素从堆上删除,pop_top_val(id), 返回堆顶元素(id, val)二元组
6. 直接通过节点的id号获取数值 get_val(id)

注意:
所有id都由左偏树数据结构自己内部生成,外部只能获取,不能自己任意创建id号
0号节点表示无效的含义,所有有效节点的id号都大于等于1
        id = 0的节点所在的堆代表空堆

代码模板以 2714. 左偏树 题目为蓝本

输入数据格式

1 a,在集合中插入一个新堆,堆中只包含一个数 a。
2 x y,将第 x 个插入的数和第 y 个插入的数所在的小根堆合并。数据保证两个数均未被删除。若两数已在同一堆中,则忽略此操作。
3 x,输出第 x 个插入的数所在小根堆的最小值。数据保证该数未被删除。
4 x,删除第 x 个插入的数所在小根堆的最小值(若最小值不唯一,则优先删除先插入的数)。数据保证该数未被删除。

*/


#include <stdio.h>
#include <algorithm>
using namespace std;

const int MAX_NODE_NUN = 200100;


int ll[MAX_NODE_NUN];       // 左指针
int rr[MAX_NODE_NUN];       // 右指针
int val[MAX_NODE_NUN];      // 实际问题里面节点数值不一定就是int类型,实际使用的时候根据具体情况做修改, 和模板参数T对应即可,需要T类型实现小于运算符
int dis[MAX_NODE_NUN];      // 节点的距离
int pp[MAX_NODE_NUN];       // 并查集数组
int __buffer[MAX_NODE_NUN]; // 缓存,并查集操作时候使用
int __pool_pos = 1;         // 当前对象池的末尾


template<typename T>
class LeftistHeapForest {
    // 获取并查集中的root id
    int __get_union_find_root(int node) {
        int pos = 0;
        int p = node;
        while (p != pp[p]) {
            __buffer[pos++] = p;
            p = pp[p];
        }

        for (int i = 0; i < pos; i++) {
            pp[__buffer[i]] = p;
        }
        return p;
    }

    // 是否在同一个并查集中
    bool __in_same_union_find(int id1, int id2) {
        return __get_union_find_root(id1) == __get_union_find_root(id2);
    }

    // 并查集合并
    void __merge_union_find(int id1, int id2) {
        pp[__get_union_find_root(id1)] = __get_union_find_root(id2);
    }

    // 并查集修改根id
    void __change_union_find_root_id(int id) {
        pp[__get_union_find_root(id)] = id;
        pp[id] = id;
    }

    // 合并根是id1和根是id2的两个堆, 返回新堆的堆顶节点的id号
    int __merge(int id1, int id2) {
        if (id1 == 0 || id2 == 0) {
            return id1 + id2;
        }

        T val1 = val[id1], val2 = val[id2];
        if (val2 < val1 || (val1 == val2 && id2 < id1)) {
            id1 = id1 ^ id2, id2 = id1 ^ id2, id1 = id1 ^ id2;
        }

        int new_root_id = __merge(rr[id1], id2);
        rr[id1] = new_root_id;
        dis[id1] = dis[new_root_id] + 1;

        int l_id = ll[id1], r_id = rr[id1];
        if (dis[r_id] > dis[l_id]) {
            ll[id1] = r_id, rr[id1] = l_id;
        }

        return id1;
    }

public:

    // 用数值v创建新堆, 返回新创建的节点的id
    int make_new_heap(T v) {
        int id = __pool_pos++;
        ll[id] = rr[id] = dis[id] = 0;
        val[id] = v;
        pp[id] = id;
        return id;
    }

    // 合并id1所在的堆和id2所在的堆, 返回新的堆的堆顶元素的id
    int merge(int id1, int id2) {
        if (id1 == 0 || id2 == 0) {
            return __get_union_find_root(id1 + id2);
        }

        if (__in_same_union_find(id1, id2)) {
            return __get_union_find_root(id1);
        }

        int new_root_id = __merge(__get_union_find_root(id1), __get_union_find_root(id2));
        __merge_union_find(id1, id2);                   // 两棵树在并查集里面合并在一起
        __change_union_find_root_id(new_root_id);       // 新的根所在的并查集的根换成这个新根自己

        return new_root_id;
    }

    // 将新数值val放入编号为id的节点存在的左偏树, 返回新节点id号
    int put(int id, T val) {
        int new_id = make_new_heap(val);
        merge(new_id, id);
        return new_id;
    }

    // 获取编号是id的节点所在堆的堆顶的元素(堆顶id, 堆顶val)
    pair<int, T> get_top_val(int id) {
        int top_id = __get_union_find_root(id);
        return pair<int, T>{top_id, val[top_id]};
    }

    // 将id号所在的堆的堆顶元素从堆上删除, 返回堆顶的元素(堆顶id, 堆顶val)
    pair<int, T> pop_top_val(int id) {
        int top_id = __get_union_find_root(id);
        int new_root_id = __merge(ll[top_id], rr[top_id]);
        __change_union_find_root_id(new_root_id);
        return pair<int, T>{top_id, val[top_id]};
    }
};

int main() {
    //freopen("/Users/grh/Programming/CLionProjects/SimpleTest/input.txt", "r", stdin);

    LeftistHeapForest<int> algo;

    int n, op;
    int a, b;
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &op);
        switch(op) {
            case 1:
                scanf("%d", &a);
                algo.make_new_heap(a);
                break;
            case 2:
                scanf("%d %d", &a, &b);
                algo.merge(a, b);
                break;
            case 3:
                scanf("%d", &a);
                printf("%d\n", algo.get_top_val(a).second);
                break;
            case 4:
                scanf("%d", &a);
                algo.pop_top_val(a);
                break;

            default:
                break;
        }
    }

    return 0;
}

2646. 棘手的操作

在这里插入图片描述

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=3e5+10;
struct qnode{
	char op[3];
	int x,y;
}p[N];
struct node{
	int to,next;
}a[N];
int n,q,tot,cnt,ls[N],w[N],fa[N],l[N],r[N];
struct SegTree{
	int w[N<<2],lazy[N<<2];
	void Downdata(int x){
		if(!lazy[x])return;
		w[x*2]+=lazy[x];lazy[x*2]+=lazy[x];
		w[x*2+1]+=lazy[x];lazy[x*2+1]+=lazy[x];
		lazy[x]=0;return;
	}
	void Change(int x,int L,int R,int l,int r,int val){
		if(L==l&&R==r){w[x]+=val;lazy[x]+=val;return;}
		int mid=(L+R)>>1;Downdata(x);
		if(r<=mid)Change(x*2,L,mid,l,r,val);
		else if(l>mid)Change(x*2+1,mid+1,R,l,r,val);
		else Change(x*2,L,mid,l,mid,val),Change(x*2+1,mid+1,R,mid+1,r,val);
		w[x]=max(w[x*2],w[x*2+1]);return;
	}
	int Ask(int x,int L,int R,int l,int r){
		if(L==l&&R==r){return w[x];}
		int mid=(L+R)>>1;Downdata(x);
		if(r<=mid)return Ask(x*2,L,mid,l,r);
		if(l>mid)return Ask(x*2+1,mid+1,R,l,r);
		return max(Ask(x*2,L,mid,l,mid),Ask(x*2+1,mid+1,R,mid+1,r));
	}
}T;
void addl(int x,int y){
	if(x==y)return;
	a[++tot].to=y;
	a[tot].next=ls[x];
	ls[x]=tot;return;
}
int find(int x)
{return (fa[x]==x)?x:(fa[x]=find(fa[x]));}
void dfs(int x){
	l[x]=r[x]=++cnt;T.Change(1,1,n,cnt,cnt,w[x]);
	for(int i=ls[x];i;i=a[i].next)
		dfs(a[i].to);
	return;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&w[i]),fa[i]=i;
	scanf("%d",&q);
	for(int i=1;i<=q;i++){
		scanf("%s",p[i].op);
		if(p[i].op[0]=='U'){
			scanf("%d%d",&p[i].x,&p[i].y);
			p[i].x=find(p[i].x);p[i].y=find(p[i].y);
			if(p[i].x==p[i].y)continue;
			if(p[i].x<p[i].y)swap(p[i].x,p[i].y);
			fa[p[i].y]=p[i].x;
		}
		else if(p[i].op[0]=='A'&&p[i].op[1]=='1')
			scanf("%d%d",&p[i].x,&p[i].y);
		else if(p[i].op[0]=='A'&&p[i].op[1]=='2')
			scanf("%d%d",&p[i].x,&p[i].y);
		else if(p[i].op[0]!='F'||p[i].op[1]!='3')
			scanf("%d",&p[i].x);
	}
	for(int i=q;i>=1;i--)
		if(p[i].op[0]=='U')addl(p[i].x,p[i].y);
	for(int i=1;i<=n;i++)
		if(find(i)==i)dfs(i);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=q;i++){
		int x=p[i].x,y=p[i].y;
		if(p[i].op[0]=='U'){
			if(x==y)continue;
			fa[y]=x;r[x]=r[y];
		}
		else if(p[i].op[0]=='A'&&p[i].op[1]=='1')
			T.Change(1,1,n,l[x],l[x],y);
		else if(p[i].op[0]=='A'&&p[i].op[1]=='2')
			x=find(x),T.Change(1,1,n,l[x],r[x],y);
		else if(p[i].op[0]=='A'&&p[i].op[1]=='3')
			T.Change(1,1,n,1,n,x);
		else if(p[i].op[0]=='F'&&p[i].op[1]=='1')
			printf("%d\n",T.Ask(1,1,n,l[x],l[x]));
		else if(p[i].op[0]=='F'&&p[i].op[1]=='2')
			x=find(x),printf("%d\n",T.Ask(1,1,n,l[x],r[x]));
		else printf("%d\n",T.Ask(1,1,n,1,n));
	}
	return 0;
}

AcWing 2725. 数字序列

在这里插入图片描述


#include <stdio.h>
#include <algorithm>
using namespace std;


const int MAX_NODE_NUN = 2001000;

struct range_node {
    int start, end;      // 区间开始和结束位置
    int root_id;        // 左偏树上的根id
} ranges[MAX_NODE_NUN];
int range_idx = 0;


int ll[MAX_NODE_NUN];       // 左指针
int rr[MAX_NODE_NUN];       // 右指针
long long val[MAX_NODE_NUN];      // 实际问题里面节点数值不一定就是int类型,实际使用的时候根据具体情况做修改, 和模板参数T对应即可,需要T类型实现小于运算符
int dis[MAX_NODE_NUN];      // 节点的距离
int pp[MAX_NODE_NUN];       // 并查集数组
int __buffer[MAX_NODE_NUN]; // 缓存,并查集操作时候使用
int __pool_pos = 1;         // 当前对象池的末尾


long long arr[MAX_NODE_NUN];

template<typename T>
class LeftistHeapForest {
    // 合并根是id1和根是id2的两个堆, 返回新堆的堆顶节点的id号
    int __merge(int id1, int id2) {
        if (id1 == 0 || id2 == 0) {
            return id1 + id2;
        }

        T val1 = val[id1], val2 = val[id2];
        if (val2 > val1 || (val1 == val2 && id2 < id1)) {
            id1 = id1 ^ id2, id2 = id1 ^ id2, id1 = id1 ^ id2;
        }

        int new_root_id = __merge(rr[id1], id2);
        rr[id1] = new_root_id;
        dis[id1] = dis[new_root_id] + 1;

        int l_id = ll[id1], r_id = rr[id1];
        if (dis[r_id] > dis[l_id]) {
            ll[id1] = r_id, rr[id1] = l_id;
        }

        return id1;
    }

public:

    LeftistHeapForest() {
        __pool_pos = 1;
    }

    // 用数值v创建新堆, 返回新创建的节点的id
    int make_new_heap(T v) {
        int id = __pool_pos++;
        ll[id] = rr[id] = dis[id] = 0;
        val[id] = v;
        return id;
    }

    // 合并堆顶是root1和堆顶是root2的堆, 返回新的堆的堆顶元素的id
    int merge(int root1, int root2) {
        return __merge(root1, root2);
    }

    // 获取堆顶是id的节点所在堆的堆顶的元素(堆顶id, 堆顶val)
    T get_top_val(int root) {
        return val[root];
    }

    // 将堆顶是root号节点的堆的堆顶元素从堆上删除, 返回(新堆顶id, 弹出去的堆顶val)
    pair<int, T> pop_top_val(int root) {
        int new_root_id = __merge(ll[root], rr[root]);
        return pair<int, T>{new_root_id, val[root]};
    }
};

int main() {
    //freopen("/Users/grh/Programming/CLionProjects/SimpleTest/input.txt", "r", stdin);


    int n;
    long long node_val, prev_val;
    scanf("%d", &n);
    LeftistHeapForest<long long> algo;

    scanf("%lld", &node_val);
    arr[0] = node_val;
    ranges[range_idx++] = {0, 0, algo.make_new_heap(node_val)};


    for (int i = 1; i < n; i++) {
        scanf("%lld", &node_val);
        arr[i] = node_val;
        node_val -= i;      // a'[i] = a[i] - i 做映射

        ranges[range_idx++] = {i, i, algo.make_new_heap(node_val)};
        while (range_idx >= 2) {
            node_val = val[ranges[range_idx-1].root_id];
            prev_val = val[ranges[range_idx-2].root_id];

            if (node_val >= prev_val) {
                break;
            }

            int new_root = algo.merge(ranges[range_idx-1].root_id, ranges[range_idx-2].root_id);
            if (((ranges[range_idx-1].end - ranges[range_idx-1].start + 1) & 1) && ((ranges[range_idx-2].end - ranges[range_idx-2].start + 1) & 1)) {
                // 两个左偏树大小都是奇数,维护中位数,合并后新的堆会多一个节点,
                new_root = algo.pop_top_val(new_root).first;
            }

            ranges[range_idx-2] = {ranges[range_idx-2].start, ranges[range_idx-1].end, new_root};
            range_idx -= 1;
        }
    }

    long long ans = 0;
    for (int i = 0; i < range_idx; i++) {
        long long top_val = val[ranges[i].root_id];
        for (int j = ranges[i].start; j <= ranges[i].end; j++) {
            ans += abs((top_val + j) - arr[j]);
        }
    }

    printf("%lld\n", ans);
    for (int i = 0; i < range_idx; i++) {
        long long top_val = val[ranges[i].root_id];     // 堆顶是区段的中位数,也就是这一整个区间的b'[i]的解
        for (int j = ranges[i].start; j <= ranges[i].end; j++) {
            printf("%lld ", top_val+j);     // b[i] = b'[i] + i, 映射回原始a序列对应的解b序列
        }
    }

    printf("\n");
}

AcWing 2721. K-单调

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
int f[N][11], cost[N][N];
int w[N], v[N], dist[N], l[N], r[N];
struct Segment
{
    int root;
    int tot_sum, tot_size;
    int tree_sum, tree_size;

    int get_cost()
    {
        int mid = v[root];
        return mid * tree_size - tree_sum + tot_sum - tree_sum - (tot_size - tree_size) * mid;
    }
}stk[N];

int merge(int x, int y)
{
    if (!x || !y) return x + y;
    if (v[x] < v[y]) swap(x, y);
    r[x] = merge(r[x], y);
    if (dist[l[x]] < dist[r[x]]) swap(l[x], r[x]);
    dist[x] = dist[r[x]] + 1;
    return x;
}

int pop(int x)
{
    return merge(l[x], r[x]);
}

void get_cost(int u)
{
    int tt = 0, res = 0;
    for (int i = u; i <= n; i ++ )
    {
        auto cur = Segment({i, v[i], 1, v[i], 1});
        l[i] = r[i] = 0, dist[i] = 1;
        while (tt && v[cur.root] < v[stk[tt].root])
        {
            res -= stk[tt].get_cost();
            cur.root = merge(cur.root, stk[tt].root);
            bool is_pop = cur.tot_size % 2 && stk[tt].tot_size % 2;
            cur.tot_size += stk[tt].tot_size;
            cur.tot_sum += stk[tt].tot_sum;
            cur.tree_size += stk[tt].tree_size;
            cur.tree_sum += stk[tt].tree_sum;
            if (is_pop)
            {
                cur.tree_size --;
                cur.tree_sum -= v[cur.root];
                cur.root = pop(cur.root);
            }
            tt -- ;
        }
        stk[ ++ tt] = cur;
        res += cur.get_cost();
        cost[u][i] = min(cost[u][i], res);
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);
    memset(cost, 0x3f, sizeof cost);
    for (int i = 1; i <= n; i ++ ) v[i] = w[i] - i;
    for (int i = 1; i <= n; i ++ ) get_cost(i);
    for (int i = 1; i <= n; i ++ ) v[i] = -w[i] - i;
    for (int i = 1; i <= n; i ++ ) get_cost(i);

    memset(f, 0x3f, sizeof f);
    f[0][0] = 0;
    for (int i = 1; i <= n; i ++ )
        for (int j = 1; j <= m; j ++ )
            for (int k = 1; k <= i; k ++ )
                f[i][j] = min(f[i][j], f[i - k][j - 1] + cost[i - k + 1][i]);

    printf("%d\n", f[n][m]);
    return 0;
}

后缀数组

AcWing 2715. 后缀数组

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 1000010;

int n, m;
char s[N];
int sa[N], x[N], y[N], c[N], rk[N], height[N];

void get_sa()
{
    for (int i = 1; i <= n; i ++ ) c[x[i] = s[i]] ++ ;
    for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
    for (int i = n; i; i -- ) sa[c[x[i]] -- ] = i;
    for (int k = 1; k <= n; k <<= 1)
    {
        int num = 0;
        for (int i = n - k + 1; i <= n; i ++ ) y[ ++ num] = i;
        for (int i = 1; i <= n; i ++ )
            if (sa[i] > k)
                y[ ++ num] = sa[i] - k;
        for (int i = 1; i <= m; i ++ ) c[i] = 0;
        for (int i = 1; i <= n; i ++ ) c[x[i]] ++ ;
        for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
        for (int i = n; i; i -- ) sa[c[x[y[i]]] -- ] = y[i], y[i] = 0;
        swap(x, y);
        x[sa[1]] = 1, num = 1;
        for (int i = 2; i <= n; i ++ )
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++ num;
        if (num == n) break;
        m = num;
    }
}

void get_height()
{
    for (int i = 1; i <= n; i ++ ) rk[sa[i]] = i;
    for (int i = 1, k = 0; i <= n; i ++ )
    {
        if (rk[i] == 1) continue;
        if (k) k -- ;
        int j = sa[rk[i] - 1];
        while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k ++ ;
        height[rk[i]] = k;
    }
}

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1), m = 122;
    get_sa();
    get_height();

    for (int i = 1; i <= n; i ++ ) printf("%d ", sa[i]);
    puts("");
    for (int i = 1; i <= n; i ++ ) printf("%d ", height[i]);
    puts("");
    return 0;
}

AcWing 1004. 品酒大会

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>

#define x first
#define y second

using namespace std;

typedef long long LL;
typedef pair<LL, LL> PLL;

const int N = 300010;
const LL INF = 2e18;

int n, m;
char s[N];
int sa[N], x[N], y[N], c[N], rk[N], height[N];
int w[N], p[N], sz[N];
LL max1[N], max2[N], min1[N], min2[N];
vector<int> hs[N];
PLL ans[N];

void get_sa()
{
    for (int i = 1; i <= n; i ++ ) c[x[i] = s[i]] ++ ;
    for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
    for (int i = n; i; i -- ) sa[c[x[i]] -- ] = i;
    for (int k = 1; k <= n; k <<= 1)
    {
        int num = 0;
        for (int i = n - k + 1; i <= n; i ++ ) y[ ++ num] = i;
        for (int i = 1; i <= n; i ++ )
            if (sa[i] > k)
                y[ ++ num] = sa[i] - k;
        for (int i = 1; i <= m; i ++ ) c[i] = 0;
        for (int i = 1; i <= n; i ++ ) c[x[i]] ++ ;
        for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
        for (int i = n; i; i -- ) sa[c[x[y[i]]] -- ] = y[i], y[i] = 0;
        swap(x, y);
        x[sa[1]] = 1, num = 1;
        for (int i = 2; i <= n; i ++ )
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++ num;
        if (num == n) break;
        m = num;
    }
}

void get_height()
{
    for (int i = 1; i <= n; i ++ ) rk[sa[i]] = i;
    for (int i = 1, k = 0; i <= n; i ++ )
    {
        if (rk[i] == 1) continue;
        if (k) k -- ;
        int j = sa[rk[i] - 1];
        while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k ++ ;
        height[rk[i]] = k;
    }
}

int find(int x)
{
    if (p[x] != x) p[x] = find(p[x]);
    return p[x];
}

LL get(int x)
{
    return x * (x - 1ll) / 2;
}

PLL calc(int r)
{
    static LL cnt = 0, maxv = -INF;

    for (auto x: hs[r])
    {
        int a = find(x - 1), b = find(x);
        cnt -= get(sz[a]) + get(sz[b]);
        p[a] = b;
        sz[b] += sz[a];
        cnt += get(sz[b]);
        if (max1[a] >= max1[b])
        {
            max2[b] = max(max1[b], max2[a]);
            max1[b] = max1[a];
        }
        else if (max1[a] > max2[b]) max2[b] = max1[a];
        if (min1[a] <= min1[b])
        {
            min2[b] = min(min1[b], min2[a]);
            min1[b] = min1[a];
        }
        else if (min1[a] < min2[b]) min2[b] = min1[a];
        maxv = max(maxv, max(max1[b] * max2[b], min1[b] * min2[b]));
    }

    if (maxv == -INF) return {cnt, 0};
    return {cnt, maxv};
}

int main()
{
    scanf("%d", &n), m = 122;
    scanf("%s", s + 1);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &w[i]);

    get_sa();
    get_height();
    for (int i = 2; i <= n; i ++ ) hs[height[i]].push_back(i);

    for (int i = 1; i <= n; i ++ )
    {
        p[i] = i, sz[i] = 1;
        max1[i] = min1[i] = w[sa[i]];
        max2[i] = -INF, min2[i] = INF;
    }

    for (int i = n - 1; i >= 0; i -- ) ans[i] = calc(i);
    for (int i = 0; i < n; i ++ ) printf("%lld %lld\n", ans[i].x, ans[i].y);

    return 0;
}

AcWing 2572. 生成魔咒

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_map>

using namespace std;

typedef long long LL;

const int N = 100010;

int n, m;
int s[N];
int sa[N], x[N], y[N], c[N], rk[N], height[N];
int u[N], d[N];
LL ans[N];

int get(int x)
{
    static unordered_map<int, int> hash;
    if (hash.count(x) == 0) hash[x] = ++ m;
    return hash[x];
}

void get_sa()
{
    for (int i = 1; i <= n; i ++ ) c[x[i] = s[i]] ++ ;
    for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
    for (int i = n; i; i -- ) sa[c[x[i]] -- ] = i;
    for (int k = 1; k <= n; k <<= 1)
    {
        int num = 0;
        for (int i = n - k + 1; i <= n; i ++ ) y[ ++ num] = i;
        for (int i = 1; i <= n; i ++ )
            if (sa[i] > k)
                y[ ++ num] = sa[i] - k;
        for (int i = 1; i <= m; i ++ ) c[i] = 0;
        for (int i = 1; i <= n; i ++ ) c[x[i]] ++ ;
        for (int i = 2; i <= m; i ++ ) c[i] += c[i - 1];
        for (int i = n; i; i -- ) sa[c[x[y[i]]] -- ] = y[i], y[i] = 0;
        swap(x, y);
        x[sa[1]] = 1, num = 1;
        for (int i = 2; i <= n; i ++ )
            x[sa[i]] = (y[sa[i]] == y[sa[i - 1]] && y[sa[i] + k] == y[sa[i - 1] + k]) ? num : ++ num;
        if (num == n) break;
        m = num;
    }
}

void get_height()
{
    for (int i = 1; i <= n; i ++ ) rk[sa[i]] = i;
    for (int i = 1, k = 0; i <= n; i ++ )
    {
        if (rk[i] == 1) continue;
        if (k) k -- ;
        int j = sa[rk[i] - 1];
        while (i + k <= n && j + k <= n && s[i + k] == s[j + k]) k ++ ;
        height[rk[i]] = k;
    }
}

int main()
{
    scanf("%d", &n);
    for (int i = n; i; i -- ) scanf("%d", &s[i]), s[i] = get(s[i]);

    get_sa();
    get_height();

    LL res = 0;
    for (int i = 1; i <= n; i ++ )
    {
        res += n - sa[i] + 1 - height[i];
        u[i] = i - 1, d[i] = i + 1;
    }
    d[0] = 1, u[n + 1] = n;

    for (int i = 1; i <= n; i ++ )
    {
        ans[i] = res;
        int k = rk[i], j = d[k];
        res -= n - sa[k] + 1 - height[k];
        res -= n - sa[j] + 1 - height[j];
        height[j] = min(height[j], height[k]);
        res += n - sa[j] + 1 - height[j];
        d[u[k]] = d[k], u[d[k]] = u[k];
    }

    for (int i = n; i; i -- ) printf("%lld\n", ans[i]);

    return 0;
}

后缀自动机

在这里插入图片描述

AcWing 2766. 后缀自动机

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 2000010;

int tot = 1, last = 1;
struct Node
{
    int len, fa;
    int ch[26];
}node[N];
char str[N];
LL f[N], ans;
int h[N], e[N], ne[N], idx;

void extend(int c)
{
    int p = last, np = last = ++ tot;
    f[tot] = 1;
    node[np].len = node[p].len + 1;
    for (; p && !node[p].ch[c]; p = node[p].fa) node[p].ch[c] = np;
    if (!p) node[np].fa = 1;
    else
    {
        int q = node[p].ch[c];
        if (node[q].len == node[p].len + 1) node[np].fa = q;
        else
        {
            int nq = ++ tot;
            node[nq] = node[q], node[nq].len = node[p].len + 1;
            node[q].fa = node[np].fa = nq;
            for (; p && node[p].ch[c] == q; p = node[p].fa) node[p].ch[c] = nq;
        }
    }
}

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        dfs(e[i]);
        f[u] += f[e[i]];
    }
    if (f[u] > 1) ans = max(ans, f[u] * node[u].len);
}

int main()
{
    scanf("%s", str);
    for (int i = 0; str[i]; i ++ ) extend(str[i] - 'a');
    memset(h, -1, sizeof h);
    for (int i = 2; i <= tot; i ++ ) add(node[i].fa, i);
    dfs(1);
    printf("%lld\n", ans);

    return 0;
}

AcWing 1283. 玄武密码

在这里插入图片描述

#include <iostream>
#include <string>
#include <algorithm>

using namespace std;

const int N = 10000010;

int n, m;
int tot = 1, last = 1;
char str[N];
struct Node
{
    int len, fa;
    int ch[4];
}node[N * 2];

inline int get(char c)
{
    if (c == 'E') return 0;
    if (c == 'S') return 1;
    if (c == 'W') return 2;
    return 3;
}

void extend(int c)
{
    int p = last, np = last = ++ tot;
    node[np].len = node[p].len + 1;
    for (; p && !node[p].ch[c]; p = node[p].fa) node[p].ch[c] = np;
    if (!p) node[np].fa = 1;
    else
    {
        int q = node[p].ch[c];
        if (node[q].len == node[p].len + 1) node[np].fa = q;
        else
        {
            int nq = ++ tot;
            node[nq] = node[q], node[nq].len = node[p].len + 1;
            node[q].fa = node[np].fa = nq;
            for (; p && node[p].ch[c] == q; p = node[p].fa) node[p].ch[c] = nq;
        }
    }
}

int main()
{
    scanf("%d%d", &n, &m);
    scanf("%s", str);
    for (int i = 0; str[i]; i ++ ) extend(get(str[i]));
    while (m -- )
    {
        scanf("%s", str);
        int p = 1, res = 0;
        for (int i = 0; str[i]; i ++ )
        {
            int c = get(str[i]);
            if (node[p].ch[c]) p = node[p].ch[c], res ++ ;
            else break;
        }
        printf("%d\n", res);
    }

    return 0;
}

AcWing 2811. 最长公共子串

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 20010;

int n;
int tot = 1, last = 1;
char str[N];
struct Node
{
    int len, fa;
    int ch[26];
}node[N];
int ans[N], now[N];
int h[N], e[N], ne[N], idx;

void extend(int c)
{
    int p = last, np = last = ++ tot;
    node[np].len = node[p].len + 1;
    for (; p && !node[p].ch[c]; p = node[p].fa) node[p].ch[c] = np;
    if (!p) node[np].fa = 1;
    else
    {
        int q = node[p].ch[c];
        if (node[q].len == node[p].len + 1) node[np].fa = q;
        else
        {
            int nq = ++ tot;
            node[nq] = node[q], node[nq].len = node[p].len + 1;
            node[q].fa = node[np].fa = nq;
            for (; p && node[p].ch[c] == q; p = node[p].fa) node[p].ch[c] = nq;
        }
    }
}

void add(int a, int b)
{
    e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}

void dfs(int u)
{
    for (int i = h[u]; ~i; i = ne[i])
    {
        dfs(e[i]);
        now[u] = max(now[u], now[e[i]]);
    }
}

int main()
{
    scanf("%d", &n);
    scanf("%s", str);
    for (int i = 0; str[i]; i ++ ) extend(str[i] - 'a');
    for (int i = 1; i <= tot; i ++ ) ans[i] = node[i].len;
    memset(h, -1, sizeof h);
    for (int i = 2; i <= tot; i ++ ) add(node[i].fa, i);

    for (int i = 0; i < n - 1; i ++ )
    {
        scanf("%s", str);
        memset(now, 0, sizeof now);
        int p = 1, t = 0;
        for (int j = 0; str[j]; j ++ )
        {
            int c = str[j] - 'a';
            while (p > 1 && !node[p].ch[c]) p = node[p].fa, t = node[p].len;
            if (node[p].ch[c]) p = node[p].ch[c], t ++ ;
            now[p] = max(now[p], t);
        }
        dfs(1);
        for (int j = 1; j <= tot; j ++ ) ans[j] = min(ans[j], now[j]);
    }

    int res = 0;
    for (int i = 1; i <= tot; i ++ ) res = max(res, ans[i]);
    printf("%d\n", res);

    return 0;
}

点分治

AcWing 252. 树

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 10010, M = N * 2;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
bool st[N];
int p[N], q[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int get_size(int u, int fa)  // 求子树大小
{
    if (st[u]) return 0;
    int res = 1;
    for (int i = h[u]; ~i; i = ne[i])
        if (e[i] != fa)
            res += get_size(e[i], u);
    return res;
}

int get_wc(int u, int fa, int tot, int& wc)  // 求重心
{
    if (st[u]) return 0;
    int sum = 1, ms = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        int t = get_wc(j, u, tot, wc);
        ms = max(ms, t);
        sum += t;
    }
    ms = max(ms, tot - sum);
    if (ms <= tot / 2) wc = u;
    return sum;
}

void get_dist(int u, int fa, int dist, int& qt)
{
    if (st[u]) return;
    q[qt ++ ] = dist;
    for (int i = h[u]; ~i; i = ne[i])
        if (e[i] != fa)
            get_dist(e[i], u, dist + w[i], qt);
}

int get(int a[], int k)
{
    sort(a, a + k);
    int res = 0;
    for (int i = k - 1, j = -1; i >= 0; i -- )
    {
        while (j + 1 < i && a[j + 1] + a[i] <= m) j ++ ;
        j = min(j, i - 1);
        res += j + 1;
    }
    return res;
}

int calc(int u)
{
    if (st[u]) return 0;
    int res = 0;
    get_wc(u, -1, get_size(u, -1), u);
    st[u] = true;  // 删除重心

    int pt = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i], qt = 0;
        get_dist(j, -1, w[i], qt);
        res -= get(q, qt);
        for (int k = 0; k < qt; k ++ )
        {
            if (q[k] <= m) res ++ ;
            p[pt ++ ] = q[k];
        }
    }
    res += get(p, pt);

    for (int i = h[u]; ~i; i = ne[i]) res += calc(e[i]);
    return res;
}

int main()
{
    while (scanf("%d%d", &n, &m), n || m)
    {
        memset(st, 0, sizeof st);
        memset(h, -1, sizeof h);
        idx = 0;
        for (int i = 0; i < n - 1; i ++ )
        {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            add(a, b, c), add(b, a, c);
        }

        printf("%d\n", calc(0));
    }

    return 0;
}

AcWing 264. 权值

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

#define x first
#define y second

using namespace std;

typedef pair<int, int> PII;

const int N = 200010, M = N * 2, S = 1000010, INF = 0x3f3f3f3f;

int n, m;
int h[N], e[M], w[M], ne[M], idx;
int f[S], ans = INF;
PII p[N], q[N];
bool st[N];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int get_size(int u, int fa)
{
    if (st[u]) return 0;
    int res = 1;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        res += get_size(j, u);
    }
    return res;
}

int get_wc(int u, int fa, int tot, int& wc)
{
    if (st[u]) return 0;
    int sum = 1, ms = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        int t = get_wc(j, u, tot, wc);
        ms = max(ms, t);
        sum += t;
    }
    ms = max(ms, tot - sum);
    if (ms <= tot / 2) wc = u;
    return sum;
}

void get_dist(int u, int fa, int dist, int cnt, int& qt)
{
    if (st[u] || dist > m) return;
    q[qt ++ ] = {dist, cnt};
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        get_dist(j, u, dist + w[i], cnt + 1, qt);
    }
}

void calc(int u)
{
    if (st[u]) return;
    get_wc(u, -1, get_size(u, -1), u);
    st[u] = true;

    int pt = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i], qt = 0;
        get_dist(j, u, w[i], 1, qt);
        for (int k = 0; k < qt; k ++ )
        {
            auto& t = q[k];
            if (t.x == m) ans = min(ans, t.y);
            ans = min(ans, f[m - t.x] + t.y);
            p[pt ++ ] = t;
        }
        for (int k = 0; k < qt; k ++ )
        {
            auto& t = q[k];
            f[t.x] = min(f[t.x], t.y);
        }
    }
    for (int i = 0; i < pt; i ++ )
        f[p[i].x] = INF;

    for (int i = h[u]; ~i; i = ne[i]) calc(e[i]);
}

int main()
{
    scanf("%d%d", &n, &m);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }

    memset(f, 0x3f, sizeof f);
    calc(0);

    if (ans == INF) ans = -1;
    printf("%d\n", ans);

    return 0;
}

点分树

AcWing 2226. 开店

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

typedef long long LL;

const int N = 150010, M = N * 2;

int n, m, A;
int h[N], e[M], w[M], ne[M], idx;
int age[N];
bool st[N];
struct Father
{
    int u, num;
    LL dist;
};
vector<Father> f[N];
struct Son
{
    int age;
    LL dist;
    bool operator< (const Son& t) const
    {
        return age < t.age;
    }
};
vector<Son> son[N][3];

void add(int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

int get_size(int u, int fa)
{
    if (st[u]) return 0;
    int res = 1;
    for (int i = h[u]; ~i; i = ne[i])
        if (e[i] != fa)
            res += get_size(e[i], u);
    return res;
}

int get_wc(int u, int fa, int tot, int& wc)
{
    if (st[u]) return 0;
    int sum = 1, ms = 0;
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        int t = get_wc(j, u, tot, wc);
        ms = max(ms, t);
        sum += t;
    }
    ms = max(ms, tot - sum);
    if (ms <= tot / 2) wc = u;
    return sum;
}

void get_dist(int u, int fa, LL dist, int wc, int k, vector<Son>& p)
{
    if (st[u]) return;
    f[u].push_back({wc, k, dist});
    p.push_back({age[u], dist});
    for (int i = h[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (j == fa) continue;
        get_dist(j, u, dist + w[i], wc, k, p);
    }
}

void calc(int u)
{
    if (st[u]) return;
    get_wc(u, -1, get_size(u, -1), u);
    st[u] = true;

    for (int i = h[u], k = 0; ~i; i = ne[i])
    {
        int j = e[i];
        if (st[j]) continue;
        auto& p = son[u][k];
        p.push_back({-1, 0}), p.push_back({A + 1, 0});
        get_dist(j, -1, w[i], u, k, p);
        k ++ ;
        sort(p.begin(), p.end());
        for (int i = 1; i < p.size(); i ++ ) p[i].dist += p[i - 1].dist;
    }

    for (int i = h[u]; ~i; i = ne[i]) calc(e[i]);
}

LL query(int u, int l, int r)
{
    LL res = 0;
    for (auto& t: f[u])
    {
        int g = age[t.u];
        if (g >= l && g <= r) res += t.dist;
        for (int i = 0; i < 3; i ++ )
        {
            if (i == t.num) continue;
            auto& p = son[t.u][i];
            if (p.empty()) continue;
            int a = lower_bound(p.begin(), p.end(), Son({l, -1})) - p.begin();
            int b = lower_bound(p.begin(), p.end(), Son({r + 1, -1})) - p.begin();
            res += t.dist * (b - a) + p[b - 1].dist - p[a - 1].dist;
        }
    }

    for (int i = 0; i < 3; i ++ )
    {
        auto& p = son[u][i];
        if (p.empty()) continue;
        int a = lower_bound(p.begin(), p.end(), Son({l, -1})) - p.begin();
        int b = lower_bound(p.begin(), p.end(), Son({r + 1, -1})) - p.begin();
        res += p[b - 1].dist - p[a - 1].dist;
    }

    return res;
}

int main()
{
    scanf("%d%d%d", &n, &m, &A);
    for (int i = 1; i <= n; i ++ ) scanf("%d", &age[i]);
    memset(h, -1, sizeof h);
    for (int i = 0; i < n - 1; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(a, b, c), add(b, a, c);
    }
    calc(1);
    LL res = 0;
    while (m -- )
    {
        int u, a, b;
        scanf("%d%d%d", &u, &a, &b);
        int l = (a + res) % A, r = (b + res) % A;
        if (l > r) swap(l, r);
        res = query(u, l, r);
        printf("%lld\n", res);
    }

    return 0;
}

CDQ分治

AcWing 2815. 三维偏序

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, M = 200010;

int n, m;
struct Data
{
    int a, b, c, s, res;

    bool operator< (const Data& t) const
    {
        if (a != t.a) return a < t.a;
        if (b != t.b) return b < t.b;
        return c < t.c;
    }
    bool operator== (const Data& t) const
    {
        return a == t.a && b == t.b && c == t.c;
    }
}q[N], w[N];
int tr[M], ans[N];

int lowbit(int x)
{
    return x & -x;
}

void add(int x, int v)
{
    for (int i = x; i < M; i += lowbit(i)) tr[i] += v;
}

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

void merge_sort(int l, int r)
{
    if (l >= r) return;
    int mid = l + r >> 1;
    merge_sort(l, mid), merge_sort(mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <= r)
        if (q[i].b <= q[j].b) add(q[i].c, q[i].s), w[k ++ ] = q[i ++ ];
        else q[j].res += query(q[j].c), w[k ++ ] = q[j ++ ];
    while (i <= mid) add(q[i].c, q[i].s), w[k ++ ] = q[i ++ ];
    while (j <= r) q[j].res += query(q[j].c), w[k ++ ] = q[j ++ ];
    for (i = l; i <= mid; i ++ ) add(q[i].c, -q[i].s);
    for (i = l, j = 0; j < k; i ++, j ++ ) q[i] = w[j];
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0; i < n; i ++ )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        q[i] = {a, b, c, 1};
    }
    sort(q, q + n);

    int k = 1;
    for (int i = 1; i < n; i ++ )
        if (q[i] == q[k - 1]) q[k - 1].s ++ ;
        else q[k ++ ] = q[i];

    merge_sort(0, k - 1);
    for (int i = 0; i < k; i ++ )
        ans[q[i].res + q[i].s - 1] += q[i].s;

    for (int i = 0; i < n; i ++ ) printf("%d\n", ans[i]);

    return 0;
}

AcWing 2847. 老C的任务

在这里插入图片描述

/*
 *
 * 思路,将点拓展为三维(x, y, z) z为1表示是查询中的点,z为0表示原来图上就已经有的点,
 * 每一个查询都对应的是二维差分问题,关键问题就是怎么对一个坐标点(x, y), 求xi <= x
 * 且 yi <= y的所有点的权值和,若加上z这一维度,刚好是对z=1的点(x, y, z), 求所有的
 * xi <= x and y1 <= y and zi = 0 的所有点的权值和,这样就可以转成CDQ分治来做了
 *
 */

#include <stdio.h>
#include <string.h>
#include <map>
#include <algorithm>
using namespace std;



const int MAX_NODE_NUM = 1000000;

// 三元组,表示三个维度属性值
struct Node {
    int a, b;                   // a, b对应 x, y坐标
    int c;                      // c数值为0, 1, 0表示原来图上存在的点,1表示询问中涉及到的点
    long long sum;              // 在当前点左下角的所有c为0的点的权值和
    long long wei;              // 点权值

    bool operator < (const Node& other) const {
        if (a != other.a) return a < other.a;
        if (b != other.b) return b < other.b;
        return c < other.c;
    }

    bool operator == (const Node& other) {
        return a == other.a && b == other.b && c == other.c;
    }

} buf[1000000], merge_buf[1000000], q[1000000];     // buf是输入数据的缓存,也是结果缓存,merge_buf是归并排序时候使用的额外缓存



void merge_sort(int ll, int rr) {
    if (ll == rr) {
        return;
    }

    int mid = (ll + rr) >> 1;
    merge_sort(ll, mid);
    merge_sort(mid+1, rr);

    int ii = ll - 1;
    int pos = -1;
    long long tot = 0;
    for (int jj = mid + 1; jj <= rr; jj++) {
        while (ii+1 <= mid && buf[ii+1].b <= buf[jj].b) {
            ii += 1;

            if (buf[ii].c == 0) {
                tot += buf[ii].wei;
            }

            merge_buf[++pos] = buf[ii];
        }

        if (buf[jj].c == 1) {
            buf[jj].sum += tot;
        }
        merge_buf[++pos] = buf[jj];
    }

    while (ii+1 <= mid) {
        ii += 1;
        merge_buf[++pos] = buf[ii];
    }

    // 子区间合并
    for (int i = ll; i <= rr; i++) {
        buf[i] = merge_buf[i-ll];
    }
}

// n是输入的三元组数组的长度
void cdq(int n) {
    sort(buf, buf + n);
    merge_sort(0, n-1);
}


int ans[1000000];
int main() {
    // freopen("/Users/grh/Programming/CLionProjects/SimpleTest/input.txt", "r", stdin);

    int n, m;
    int a, b, w;
    scanf("%d %d", &n, &m);

    int pos = 0;
    for (int i = 0; i < n; i++) {
        scanf("%d %d %lld", &(buf[pos].a), &(buf[pos].b), &(buf[pos].wei));
        buf[pos].c = 0;
        buf[pos].sum = 0;
        pos += 1;
    }

    int x1, y1, x2, y2;
    int q_pos = 0;
    for (int i = 0; i < m; i++) {
        scanf("%d %d %d %d", &x1, &y1, &x2, &y2);
        if (x1 > x2) {
            swap(x1, x2);
        }

        if (y1 > y2) {
            swap(y1, y2);
        }

        buf[pos].a = x2, buf[pos].b = y2, buf[pos].c = 1;
        buf[pos].wei = 0, buf[pos].sum = 0;
        q[q_pos++] = buf[pos];
        pos += 1;

        buf[pos].a = x1-1, buf[pos].b = y2, buf[pos].c = 1;
        buf[pos].wei = 0, buf[pos].sum = 0;
        q[q_pos++] = buf[pos];
        pos += 1;

        buf[pos].a = x2, buf[pos].b = y1-1, buf[pos].c = 1;
        buf[pos].wei = 0, buf[pos].sum = 0;
        q[q_pos++] = buf[pos];
        pos += 1;

        buf[pos].a = x1-1, buf[pos].b = y1-1, buf[pos].c = 1;
        buf[pos].wei = 0, buf[pos].sum = 0;
        q[q_pos++] = buf[pos];
        pos += 1;
    }

    cdq(pos);

    map<Node, long long> nodes;
    for (int i = 0; i < pos; i++) {
        if (buf[i].c == 1) {
            nodes[buf[i]] = buf[i].sum;
        }
    }

    // 用矩形的四个关键点来进行差分计算
    for (int i = 0; i < q_pos >> 2; i++) {
        long long tot = 0;
        tot += nodes[q[i * 4]];
        tot -= nodes[q[i * 4 + 1]];
        tot -= nodes[q[i * 4 + 2]];
        tot += nodes[q[i * 4 + 3]];
        printf("%lld\n", tot);
    }

    return 0;
}

AcWing 2819. 动态逆序对

在这里插入图片描述


/*
 *
 * 构造三种属性,三维偏序问题用CDQ分治算法来求解,属性a是点的权值,就是1到N的数值,属性b是点的下标,属性c是点被删除的时间点,设第一个
 * 删除的点的删除时间是N, 后面删除的点的删除时间依次递减(没有删除的点的删除时间从剩下的时间点中随机分即可)
 *
 * 假设删除时间是i的点和删除时间是j的点构成了一个逆序对(i,j), 那(j, i)也是一个逆序对,但是两个逆序对计数只能算一次,统一规定将计数累计到i
 * j中偏大的那个点上,也就是较早删除的点上,定义s[i]表示是删除时间为i的点上累加的逆序对计数,在这样的一种特殊构造出的定义之下,问题所求的"删除
 * 某点之前的逆序对数量"(假设这个点删除时间是i), 就是s[i] + s[i-1] + s[i-2] + ..... s[1], 就是一个前缀和,所以只需要考虑了怎么求s[i]
 *
 * 删除时间是i的节点可能产生两种逆序对(这里的i值就是节点中的c属性):
 *
 * 第一种,node(i)的值偏小, 和比其更晚删除的一个值偏大的点j配成逆序对,贡献累加到i节点上:
 * node(j).a > node(i).a && node(j).b < node(i).b && node(j).c < node(i).c
 *
 * 第二种, node(i)的值偏大,和比其更晚删除的一个值偏小的点j配成逆序对, 贡献累加到i节点上:
 * node(j).a < node(i).a && node(j).b > node(i).b && node(j).c < node(i).c
 *
 * (两种类型的逆序对交换角色也是没问题的,问题对称过来还是一样的)
 *
 * 对这两类逆序对,对每一个节点求出其所有能构造出的这两类逆序对,把贡献值在对应的位置上进行累加,所有的逆序对是不重不漏的
 * 所以问题就变成了两个独立的三维偏序问题(问题中的大于符号,将原属性取相反数就可以变成小于号),分别求解,然后再做一次前缀
 * 和,对于每一个询问,打印出对应的前缀和即可
 *
 */



#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;


const int MAX_FENWICK_LEN = 100005;
int sum_seg[MAX_FENWICK_LEN];

class FenwickTree {
private:
    int data_len;       // 存储的前缀和的长度

    // 获取区间[0, end]的前缀和
    int __get_sum_with_end(int end) {
        int ans = 0;
        while (end) {
            ans += sum_seg[end];
            end &= (end - 1);
        }
        return ans;
    }

public:
    FenwickTree(int n) : data_len(n) {
        for (int i = 1; i <= n; i++) {
            sum_seg[i] = 0;
        }
    }

    // 获取在x的原始数值
    int get_orig_val(int x) {
        x += 1;
        if (x < 1 || x > data_len) {
            // 异常分支
            return 0;
        }

        int ans = sum_seg[x];
        int lca = x & (x-1);
        x--;

        while (x != lca) {
            ans -= sum_seg[x];
            x &= (x-1);
        }
        return ans;
    }

    // 获取区间[start, end]的前缀和
    int get_sum(int start, int end) {
        start++, end++;
        if (! (end >= start && start >= 1 && end <= data_len)) {
            // 异常分支
            return 0;
        }

        if (start == 1) {
            return __get_sum_with_end(end);
        }
        return __get_sum_with_end(end) - __get_sum_with_end(start - 1);
    }

    // x位置的原始序列数值增加delta
    void add_val(int x, int delta) {
        x += 1;
        while (x <= data_len) {
            sum_seg[x] += delta;
            x += x & (-x);
        }
    }
};

const int MAX_NODE_NUM = 100005;

// 三元组,表示三个维度属性值
struct Node {
    int a, b, c;        // 三个维度的属性值
    int cnt;            // 原序列中数值等于(a, b, c)的节点个数
    int le_num;         // 对于数值等于(a, b, c)的原序列中的点X, 三维偏序小于等于X的原序节点的数量(不包括X节点自己)

    bool operator < (const Node& other) const {
        if (a != other.a) return a < other.a;
        if (b != other.b) return b < other.b;
        return c < other.c;
    }

    bool operator == (const Node& other) {
        return a == other.a && b == other.b && c == other.c;
    }

} buf[MAX_NODE_NUM], buf1[MAX_NODE_NUM], buf2[MAX_NODE_NUM], merge_buf[MAX_NODE_NUM];     // buf是输入数据的缓存,也是结果缓存,merge_buf是归并排序时候使用的额外缓存


// 分治解决[ll, rr]区间中的问题, 按照b属性进行归并排序
FenwickTree fenwick(100005 + 10);

// s[i]表示和删除时间戳是i的数值配对的逆序对数量
long long s[MAX_NODE_NUM];

void merge_sort(int ll, int rr) {
    if (ll == rr) {
        return;
    }

    int mid = (ll + rr) >> 1;
    merge_sort(ll, mid);
    merge_sort(mid+1, rr);


    int ii = ll - 1;
    int pos = -1;
    for (int jj = mid + 1; jj <= rr; jj++) {
        while (ii+1 <= mid && buf[ii+1].b <= buf[jj].b) {
            ii += 1;
            fenwick.add_val(buf[ii].c, buf[ii].cnt);
            merge_buf[++pos] = buf[ii];
        }

        int sum_val = fenwick.get_sum(0, buf[jj].c);
        s[buf[jj].c] += sum_val;
        buf[jj].le_num += sum_val;
        merge_buf[++pos] = buf[jj];
    }

    // 将树状数组还原回去
    for (int i = ll; i <= ii; i++) {
        fenwick.add_val(buf[i].c, -buf[i].cnt);
    }

    while (ii+1 <= mid) {
        ii += 1;
        merge_buf[++pos] = buf[ii];
    }

    // 子区间合并
    for (int i = ll; i <= rr; i++) {
        buf[i] = merge_buf[i-ll];
    }
}

// n是输入的三元组数组的长度
void cdq(int n) {
    sort(buf, buf + n);
    merge_sort(0, n-1);
}

// 数值到下标的映射
int val2idx[MAX_NODE_NUM];

int main(void) {

    int n, m;
    scanf("%d %d", &n, &m);

    int val;
    for (int i = 0; i < n; i++) {
        scanf("%d", &val);

        val2idx[val] = i;
        buf1[i].a = val;
        buf1[i].b = i;
        buf1[i].c = 0;
        buf1[i].cnt = 1;
    }

    for (int i = 0; i < m; i++) {
        scanf("%d", &val);
        buf1[val2idx[val]].c = n - i;
    }

    int rank = n-m;
    for (int i = 0; i < n; i++) {
        if (buf1[i].c == 0) {
            buf1[i].c = rank--;
        }
    }

    for (int i = 0; i < n; i++) {
        buf[i] = buf1[i];
        buf[i].a = n+1 - buf1[i].a;
    }
    cdq(n);

    for (int i = 0; i < n; i++) {
        buf[i] = buf1[i];
        buf[i].b = n+1 - buf1[i].b;
    }
    cdq(n);

    // s求前缀和
    for (int i = 1; i <= n; i++) {
        s[i] += s[i-1];
    }

    for (int i = n; i > n-m; i--) {
        printf("%lld\n", s[i]);
    }

    return 0;
}
作者:皓首不倦
链接:https://www.acwing.com/solution/content/50059/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 构造三种属性,三维偏序问题用CDQ分治算法来求解,属性a是点的权值,就是1到N的数值,属性b是点的下标,属性c是点被删除的时间点,设第一个
  • 删除的点的删除时间是N, 后面删除的点的删除时间依次递减(没有删除的点的删除时间从剩下的时间点中随机分即可)
  • 假设删除时间是i的点和删除时间是j的点构成了一个逆序对(i,j), 那(j, i)也是一个逆序对,但是两个逆序对计数只能算一次,统一规定将计数累计到i
  • j中偏大的那个点上,也就是较早删除的点上,定义s[i]表示是删除时间为i的点上累加的逆序对计数,在这样的一种特殊构造出的定义之下,问题所求的"删除
  • 某点之前的逆序对数量"(假设这个点删除时间是i), 就是s[i] + s[i-1] + s[i-2] + … s[1], 就是一个前缀和,所以只需要考虑了怎么求s[i]
  • 删除时间是i的节点可能产生两种逆序对(这里的i值就是节点中的c属性):
  • 第一种,node(i)的值偏小, 和比其更晚删除的一个值偏大的点j配成逆序对,贡献累加到i节点上:
  • node(j).a > node(i).a && node(j).b < node(i).b && node(j).c < node(i).c
  • 第二种, node(i)的值偏大,和比其更晚删除的一个值偏小的点j配成逆序对, 贡献累加到i节点上:
  • node(j).a < node(i).a && node(j).b > node(i).b && node(j).c < node(i).c
  • (两种类型的逆序对交换角色也是没问题的,问题对称过来还是一样的)
  • 对这两类逆序对,对每一个节点求出其所有能构造出的这两类逆序对,把贡献值在对应的位置上进行累加,所有的逆序对是不重不漏的
  • 所以问题就变成了两个独立的三维偏序问题(问题中的大于符号,将原属性取相反数就可以变成小于号),分别求解,然后再做一次前缀
  • 和,对于每一个询问,打印出对应的前缀和即可

仙人掌

AcWing 2863. 最短路

在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 12010, M = N * 3;

int n, m, Q, new_n;
int h1[N], h2[N], e[M], w[M], ne[M], idx;
int dfn[N], low[N], cnt;
int s[N], stot[N], fu[N], fw[N];
int fa[N][14], depth[N], d[N];
int A, B;

void add(int h[], int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void build_circle(int x, int y, int z)
{
    int sum = z;
    for (int k = y; k != x; k = fu[k])
    {
        s[k] = sum;
        sum += fw[k];
    }
    s[x] = stot[x] = sum;
    add(h2, x, ++ new_n, 0);
    for (int k = y; k != x; k = fu[k])
    {
        stot[k] = sum;
        add(h2, new_n, k, min(s[k], sum - s[k]));
    }
}

void tarjan(int u, int from)
{
    dfn[u] = low[u] = ++ cnt;
    for (int i = h1[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            fu[j] = u, fw[j] = w[i];
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if (dfn[u] < low[j]) add(h2, u, j, w[i]);
        }
        else if (i != (from ^ 1)) low[u] = min(low[u], dfn[j]);
    }
    for (int i = h1[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (dfn[u] < dfn[j] && fu[j] != u)
            build_circle(u, j, w[i]);
    }
}

void dfs_lca(int u, int father)
{
    depth[u] = depth[father] + 1;
    fa[u][0] = father;
    for (int k = 1; k <= 13; k ++ )
        fa[u][k] = fa[fa[u][k - 1]][k - 1];
    for (int i = h2[u]; ~i; i = ne[i])
    {
        int j = e[i];
        d[j] = d[u] + w[i];
        dfs_lca(j, u);
    }
}

int lca(int a, int b)
{
    if (depth[a] < depth[b]) swap(a, b);
    for (int k = 13; k >= 0; k -- )
        if (depth[fa[a][k]] >= depth[b])
            a = fa[a][k];
    if (a == b) return a;
    for (int k = 13; k >= 0; k -- )
        if (fa[a][k] != fa[b][k])
        {
            a = fa[a][k];
            b = fa[b][k];
        }
    A = a, B = b;
    return fa[a][0];
}

int main()
{
    scanf("%d%d%d", &n, &m, &Q);
    new_n = n;
    memset(h1, -1, sizeof h1);
    memset(h2, -1, sizeof h2);
    while (m -- )
    {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        add(h1, a, b, c), add(h1, b, a, c);
    }
    tarjan(1, -1);
    dfs_lca(1, 0);

    while (Q -- )
    {
        int a, b;
        scanf("%d%d", &a, &b);
        int p = lca(a, b);
        if (p <= n) printf("%d\n", d[a] + d[b] - d[p] * 2);
        else
        {
            int da = d[a] - d[A], db = d[b] - d[B];
            int l = abs(s[A] - s[B]);
            int dm = min(l, stot[A] - l);
            printf("%d\n", da + dm + db);
        }
    }

    return 0;
}

AcWing 2752. 仙人掌图

在这里插入图片描述
在这里插入图片描述

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 100010, M = N * 3, INF = 1e8;

int n, m, new_n;
int h1[N], h2[N], e[M], w[M], ne[M], idx;
int dfn[N], low[N], cnt;
int s[N], stot[N], fu[N], fw[N];
int d[N], f[N], q[N];
int ans;

void add(int h[], int a, int b, int c)
{
    e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;
}

void build_circle(int x, int y, int z)
{
    int sum = z;
    for (int k = y; k != x; k = fu[k])
    {
        s[k] = sum;
        sum += fw[k];
    }
    s[x] = stot[x] = sum;
    add(h2, x, ++ new_n, 0);
    for (int k = y; k != x; k = fu[k])
    {
        stot[k] = sum;
        add(h2, new_n, k, min(s[k], sum - s[k]));
    }
}

void tarjan(int u, int from)
{
    dfn[u] = low[u] = ++ cnt;
    for (int i = h1[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (!dfn[j])
        {
            fu[j] = u, fw[j] = w[i];
            tarjan(j, i);
            low[u] = min(low[u], low[j]);
            if (dfn[u] < low[j]) add(h2, u, j, w[i]);
        }
        else if (i != (from ^ 1)) low[u] = min(low[u], dfn[j]);
    }
    for (int i = h1[u]; ~i; i = ne[i])
    {
        int j = e[i];
        if (dfn[u] < dfn[j] && fu[j] != u)
            build_circle(u, j, w[i]);
    }
}

int dfs(int u)
{
    int d1 = 0, d2 = 0;
    for (int i = h2[u]; ~i; i = ne[i])
    {
        int j = e[i];
        int t = dfs(j) + w[i];
        if (t >= d1) d2 = d1, d1 = t;
        else if (t > d2) d2 = t;
    }
    f[u] = d1;
    if (u <= n) ans = max(ans, d1 + d2);  // u是圆点
    else  // u是方点
    {
        int sz = 0;
        d[sz ++ ] = -INF;
        for (int i = h2[u]; ~i; i = ne[i])
            d[sz ++ ] = f[e[i]];
        for (int i = 0; i < sz; i ++ ) d[sz + i] = d[i];

        int hh = 0, tt = -1;
        for (int i = 0; i < sz * 2; i ++ )
        {
            if (hh <= tt && i - q[hh] > sz / 2) hh ++ ;
            if (hh <= tt) ans = max(ans, d[i] + i + d[q[hh]] - q[hh]);
            while (hh <= tt && d[q[tt]] - q[tt] <= d[i] - i) tt -- ;
            q[ ++ tt] = i;
        }
    }

    return f[u];
}

int main()
{
    scanf("%d%d", &n, &m);
    new_n = n;
    memset(h1, -1, sizeof h1);
    memset(h2, -1, sizeof h2);
    while (m -- )
    {
        int k, x, y;
        scanf("%d%d", &k, &x);
        for (int i = 0; i < k - 1; i ++ )
        {
            scanf("%d", &y);
            add(h1, x, y, 1), add(h1, y, x, 1);
            x = y;
        }
    }
    tarjan(1, -1);
    dfs(1);

    printf("%d\n", ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值