[CF1149C]Tree Generator™

题目

传送门

题解

首先搞明白建树的方式:遇到左括号往下走,遇到右括号往回走.现在我们要求这个构造出来的树的直径.

由于每一次都会互换两个括号的位置,所以显然树的形态是不固定的,进而如果我们每次将树构造出来跑树 \(\tt DP\) 显然不可取,这样复杂度为 \(\mathcal O(qn)\),对于 \(n\le 100000\) 以及 \(n,q\) 同阶来说直接 \(\tt TLE\).

那么我们要想一种能将快速求直径的方法,并且在树形态发生改变之后亦可以快速维护,那么此时需要对这道题的性质进行挖掘.

我们考虑,在用括号表达这个树之后,两点之间的距离是什么:

由于一个左括号往下,一个右括号往上,不难发现,对于这个括号序列的两点,他们所表示的点的距离为:

将形如 \(\tt ()\) 的可匹配括号去掉之后,剩下的括号的数量.

对于区间进行操作以及修改,不难想到用线段树维护答案.

同时,树的直径,即可表示为

任取括号序列的一段区间,在去掉可匹配括号之后,剩下来的括号的最长长度.

并且,显然有将一个序列的可匹配括号删去之后,剩下的只可能长得像 \(\tt )))))((\),可以不存在左括号或者右括号.

我们考虑合并答案,假设这个区间的左子区间有右括号 \(a\) 个,左括号 \(b\) 个,右子区间有右括号 \(c\) 个,左括号 \(b\) 个,那么这个区间从中间合并的答案就是

\[a+|b-c|+d \]

这个绝对值很烦,我们考虑将其去掉,那么,原式变成了

\[\max\{a+b-c+d,a-b+c+d\} \]

我们考虑上式需要维护的东西:

  1. \(a+b\),即一个区间在去掉可匹配括号之后的后缀右括号加上后缀左括号的最大值;
  2. \(a-b\),即一个区间在去掉可匹配括号之后的后缀右括号减去后缀左括号的最大值;
  3. \(d-c\),即一个区间在去掉可匹配括号之后的前缀左括号减去前缀右括号的最大值;
  4. \(c+d\),即一个区间在去掉可匹配括号之后的前缀左括号加上前缀右括号的最大值;

我们分别记这四个值为 \(x,y,z,k\).

现在考虑我们怎么维护这四个值,我们拿 \(x\) 举例子(其他都差不多了):

对于区间 \(i\),记其左右儿子分别为 \(ls,rs\),如果这个最优点划分在右边,那么直接就可以使用 \(rs\) 的 \(x\) 对答案进行取最优,但是,如果最优点划分在左边,即说明右边已经全部入选,那么我们需要将右边匹配完之后剩下的左括号、右括号分别记作 \(l,r\),那么,我们现在所知的就是,右边已经形如 \(\tt )))((\) 的结构,我们要在左边的哪里选择划分才能达到让 \(x\) 最大,考虑在左边选择的划分点以后有 \(a\) 的右括号,\(b\) 个左括号,那么 \(x\) 就可以表示为

\[x=a+|b-r|+l \]

用同样的方法去掉绝对值,有

\[x=\max\{a+b-r+l,a-b+r+l\} \]

同时,我们有 \(x_{ls}=a+b,y_{ls}=a-b\),由于我们是上传,那么 \(ls\) 以及 \(rs\) 的信息都是已知的,那么全部整合在一起,就有

\[x_i=\max\{x_{rs},\;x_{ls}-r_{rs}+l{rs},\;y_{ls}+l_{rs}+r_{rs}\} \]

由维护过程可知,我们还得再多维护俩值:一个区间的 \(l,r\),即将这个区间全部进行匹配之后,剩下的左括号、右括号个数,这个很好进行维护.

对于 \(y,z,k\) 的维护同理.

最终复杂度为 \(\mathcal O(n\log n+q\log n)\).

代码

# include <cstdio>
# include <cstring>
# include <algorithm>
using namespace std;
namespace Elaina{
    # define rep(i,l,r) for(int i=l, i##_end_ = r; i <= i##_end_; ++ i)
    # define fep(i,l,r) for(int i=l, i##_end_ = r; i >= i##_end_; -- i)
    # define fi first
    # define se second
    # define Endl putchar('\n')
    # define writc(x, c) fwrit(x), putchar(c)
    // # define int long long
    typedef long long ll;
    typedef pair<int, int> pii;
    typedef unsigned long long ull;
    typedef unsigned int uint;
    template<class T>inline T Max(const T x, const T y){return x < y ? y : x;}
    template<class T>inline T Min(const T x, const T y){return x < y ? x : y;}
    template<class T>inline T fab(const T x){return x < 0 ? -x : x;}
    template<class T>inline void getMax(T& x, const T y){x = Max(x, y);}
    template<class T>inline void getMin(T& x, const T y){x = Min(x, y);}
    template<class T>T gcd(const T x, const T y){return y ? gcd(y, x % y) : x;}
    template<class T>inline T readin(T x){
        x=0; int f = 0; char c;
        while((c = getchar()) < '0' || '9' < c) if(c == '-') f = 1;
        for(x = (c ^ 48); '0' <= (c = getchar()) && c <= '9'; x = (x << 1) + (x << 3) + (c ^ 48));
        return f ? -x : x;
    }
    template<class T>void fwrit(const T x){
        if(x < 0)return putchar('-'), fwrit(-x);
        if(x > 9)fwrit(x / 10); putchar(x % 10 ^ 48);
    }
}
using namespace Elaina;

const int maxn = 1e6;

char s[maxn + 5];

int n, q;

struct node{
    // 下面维护的都是最大值
    /** @brief 后缀右括号加上后缀左括号*/
    int x;
    /** @brief 后缀右括号减去后缀左括号*/
    int y;
    /** @brief 前缀左括号减去前缀右括号*/
    int z;
    /** @brief 前缀左括号加上前缀右括号*/
    int k;
    /** @brief 合并整个区间之后, 剩下的左括号*/
    int l;
    /** @brief 合并整个区间之后, 剩下的右括号*/
    int r;
    /** @brief 区间答案*/
    int ans;
}tre[maxn * 4 + 5];
# define ls (i << 1)
# define rs (i << 1 | 1)
# define mid ((l + r) >> 1)
# define _lq ls, l, mid
# define _rq rs, mid + 1, r
inline void pushup(const int i){
    if(tre[ls].l > tre[rs].r){
        tre[i].l = tre[ls].l - tre[rs].r + tre[rs].l;
        tre[i].r = tre[ls].r;
    } else {
        tre[i].l = tre[rs].l;
        tre[i].r = tre[rs].r - tre[ls].l + tre[ls].r;
    }
    tre[i].x = Max(tre[rs].x, Max(tre[ls].x + tre[rs].l - tre[rs].r, tre[ls].y + tre[rs].r + tre[rs].l));
    tre[i].y = Max(tre[rs].y, tre[ls].y + tre[rs].r - tre[rs].l);
    tre[i].z = Max(tre[ls].z, tre[ls].l - tre[ls].r + tre[rs].z);
    tre[i].k = Max(tre[ls].k, Max(tre[ls].l + tre[ls].r + tre[rs].z, tre[ls].r - tre[ls].l + tre[rs].k));
    tre[i].ans = Max(Max(tre[ls].x + tre[rs].z, tre[ls].y + tre[rs].k), Max(tre[ls].ans, tre[rs].ans));
}
/** @param x 如果是 -1 就是右括号, 如果是 1 就是左括号*/
inline void modify(const int p, const int x, const int i = 1, const int l = 1, const int r = n){
    if(l == r){
        tre[i].x = tre[i].k = 1;
        tre[i].y = Max(-x, 0);
        tre[i].z = Max(x, 0);
        tre[i].l = (x == 1), tre[i].r = (x == -1);
        tre[i].ans = 1;
        return;
    }
    if(p <= mid) modify(p, x, _lq);
    else modify(p, x, _rq);
    pushup(i);
}
void build(const int i = 1, const int l = 1, const int r = n){
    if(l == r){
        int x = (s[l] == '(') ? 1 : -1;
        tre[i].x = tre[i].k = 1;
        tre[i].y = Max(-x, 0);
        tre[i].z = Max(x, 0);
        tre[i].l = (x == 1), tre[i].r = (x == -1);
        tre[i].ans = 1;
        return;
    }
    build(_lq), build(_rq);
    pushup(i);
}

void print(const int i = 1,const int l = 1, const int r = n){
    if(l != r) print(_lq), print(_rq);
    printf("node %d :> l == %d, r == %d\n", i, l, r);
    printf("x == %d, y == %d, z == %d, k == %d, ans == %d\n", tre[i].x, tre[i].y, tre[i].z, tre[i].k, tre[i].ans);
    printf("l == %d, r == %d\n", tre[i].l, tre[i].r);
    puts("-----------------------------------------");
    
}

inline void init(){
    n = readin(1), q = readin(1);
    scanf("%s", s + 1);
    n = strlen(s + 1);
    build();
    // print();
}

signed main(){
    init();
    printf("%d\n", tre[1].ans);
    int x, y;
    while(q --){
        x = readin(1), y = readin(1);
        modify(x, s[y] == '(' ? 1 : -1);
        modify(y, s[x] == '(' ? 1 : -1);
        swap(s[x], s[y]);
        printf("%d\n", tre[1].ans);
        // print();
    }
    return 0;
}
上一篇:bzoj4919:大根堆


下一篇:6.7考试总结(NOIP模拟5)