Loading

CF1149C Tree Generator™

题意

\(n\) 个点,\(m\) 个询问。

给你一棵树的括号序列,输出它的直径。

\(m\) 次询问,每次询问表示交换两个括号,输出交换两个括号后的直径(保证每次操作后都为一棵树)

输出共 \(m + 1\) 行。

\(3 \le n \le 100\,000,1 \le q \le 100\,000\)

—— 洛谷翻译。

思路

我们不难想到,一个括号如果匹配,那么以它为根的子树中所有结点已经全部访问完毕。

所以我们可以想到,一段区间中没有匹配的括号个数就是一条路径的长度。

所以一条直径所对应的括号序列一定是由若干右括号接若干左括号构成的。

然后我们很套路地将 ) 赋值为 \(-1\),将 ( 赋值为 \(1\)

如果一段区间除了)就是两两匹配的括号,那么这一段的和为右括号数量的相反数。

如果一段区间除了(就是两两匹配的括号,那么这一段的和为左括号数量。

那么对于一段区间 \([l, r]\),如果 \([l, k]\) 除了 ) 就是两两匹配的括号,\([k + 1, r]\) 除了 ( 就是两两匹配的括号,那么 \([l, r]\) 对应的路径长度就是右括号数量 + 左括号数量(不包括匹配括号),即 \(sum(k + 1, r) - sum(l, k)\),这个就是直径,如果分割点不在 \(k\),而在 \(p\),那么有一些匹配的括号会使得答案少 2,所以 \(sum(p + 1, r) - sum(l, p) \le sum(k + 1, r) - sum(l, k)\),所以我们只要保证 \(k\) 最大,\(sum(k + 1, r) - sum(l, k)\) 一定是直径长度。

那么我们可以用线段树来在一段含 \(n\) 个数字的数列 \(a\) 中查找 \(\max\limits_{1 \le l \le r \le n}\max\limits_{l\le k< r} sum(k + 1, r) - sum(l, k)\)

线段树维护 8 个信息:

  1. 一个区间的和 sum
  2. 一个区间前缀的最大和 lsum_max
  3. 一个区间前缀的最小和 lsum_min
  4. 一个区间后缀的最大和 rsum_max
  5. 一个区间后缀的最小和 rsum_min
  6. 一个区间后一段减去前一段的最大和(\(\max\limits_{l < g \le k \le r}sum(g, k) - sum(l, g - 1)\)lans
  7. 一个区间后一段减去前一段的最大和(\(\max\limits_{l \le k < g \le r}sum(g, r) - sum(k, g - 1)\)rans
  8. 结果,\(\max\limits_{1 \le l \le r \le n}\max\limits_{l\le k< r} sum(k + 1, r) - sum(l, k)\)
    ans

image

怎么维护请看代码(懒得写了)。

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 200010;

int n, q, a[N];

struct node {
    int sum;
    int lsum_max, rsum_max;
    int lsum_min, rsum_min;
    int lans, rans;
    int ans;
} tr[N << 2];

node init(int x) {
    node nd;
    nd.sum = x;
    nd.lsum_max = nd.rsum_max = max(x, 0);
    nd.lsum_min = nd.rsum_min = min(x, 0);
    nd.lans = nd.rans = nd.ans = 1;
    return nd;
}

node pushup(node left, node right) {
    node res;
    res.sum = left.sum + right.sum;
    res.lsum_max = max(left.lsum_max, left.sum + right.lsum_max);
    res.rsum_max = max(right.rsum_max, right.sum + left.rsum_max);
    res.lsum_min = min(left.lsum_min, left.sum + right.lsum_min);
    res.rsum_min = min(right.rsum_min, right.sum + left.rsum_min);
    res.lans = max(max(left.lans, right.lans - left.sum), right.lsum_max + 2 * left.rsum_max - left.sum);
    res.rans = max(max(right.rans, right.sum + left.rans), right.sum - left.rsum_min - 2 * right.lsum_min);
    res.ans = max(max(left.ans, right.ans), max(right.lans - left.rsum_min, left.rans + right.lsum_max));
    return res;
}

void build(int u, int l, int r) {
    if (l == r) {
        tr[u] = init(a[l]);
        return;
    }

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

void modify(int u, int l, int r, int x) {
    if (l == r) {
        tr[u] = init(a[x]);
        return;
    }
    int mid = l + r >> 1;
    if (x <= mid) modify(u << 1, l, mid, x);
    else modify(u << 1 | 1, mid + 1, r, x);
    tr[u] = pushup(tr[u << 1], tr[u << 1 | 1]);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> n >> q;
    n = (n - 1) * 2;
    for (int i = 1; i <= n; i++) {
        char c;
        cin >> c;
        if (c == '(') a[i] = 1;
        else a[i] = -1;
    }

    build(1, 1, n);
    cout << tr[1].ans << '\n';
    while (q--) {
        int x, y;
        cin >> x >> y;
        swap(a[x], a[y]);
        if (a[x] != a[y]) modify(1, 1, n, x), modify(1, 1, n, y);
        cout << tr[1].ans << '\n';
    }
    return 0;
}
posted @ 2024-08-13 16:52  SunnyYuan  阅读(3)  评论(0编辑  收藏  举报