题目描述

捉迷藏 Jiajia 和 Wind 是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia、Wind 和孩子们决定在家里玩捉迷藏游戏。

他们的家很大且构造很奇特,由 $N$ 个屋子和 $N-1$ 条双向走廊组成,这 $N-1$ 条走廊的分布使得任意两个屋子都互相可达。

游戏是这样进行的:孩子们负责躲藏,Jiajia 负责找,而 Wind 负责操纵这 $N$ 个屋子的灯。在起初的时候,所有的灯都没有被打开。

每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia 希望知道可能的最远的两个孩子的距离 (即最远的两个关灯房间的距离)。

我们将以如下形式定义每一种操作:

C(hange) i 改变第 $i$ 个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。

G(ame) 开始一次游戏,查询最远的两个关灯房间的距离。

输入格式

第一行包含一个正整数 $N$ ($N \leq 10^5$),表示房间的个数,房间将被编号为 $1, 2, 3, \cdots, n$ 的整数。

接下来 $N-1$ 行每行包含两个整数 $a, b$,表示房间 $a$ 与房间 $b$ 之间有一条走廊相连。

接下来一行包含一个正整数 $Q$ ($Q \leq 5 \times 10^5$),表示操作次数。

接下来 $Q$ 行,每行一个操作,如上文所示。

输出格式

对于每一个操作 Game,输出一个非负整数,表示最远的两个关灯房间的距离。若只有一个房间是关着灯的,输出 $0$;若所有房间的灯都开着,输出 $-1$。

题解

这道题要用到括号序列的有关两点之间的路径的一个性质。

先扯括号序列的定义,一棵树的括号序列为长度为 $3n$ 的序列,每个点 $v$ 入栈时加一个 ( 和 $v$,出栈时加一个 ),如下面这棵树 (样例) 的括号序列就是 (1(2(3(4)(5)(6(7)(8)))))

一棵树

于是就有,对于任意两点 $u, v$,我们将括号序列中从 $u$ 到 $v$ 的一段 (子串) 取出来,并把数字和匹配的括号去掉,则剩余的括号数就是 $u$ 到 $v$ 的距离。比如 $4$ 和 $8$,取出的子串为 4)(5)(6(7)(8,把其中的数字和匹配的括号去掉,即得)((,剩余的括号数 $3$ 就是 $4$ 到 $8$ 的距离。

接下来要维护的就是将匹配的括号去掉后的剩余的括号数,使它们最大的一对节点。可以看出,将匹配的括号去掉后,剩下的串一定是若干个 ) 紧接着若干个 (,即 ))...)((...(。并且还有若干次修改 (切换某些点的有效和无效,其中关灯房间为有效)。

尝试使用线段树,我们要维护这么几个信息:

l (Left bracket): 该区间内 (去掉匹配括号后,下同) 剩余的 ) 数 (可以看出都在左边);

r (Right bracket): 该区间内剩余的 ( 数 (可以看出都在右边);

lc (Left bracket count): 该区间内,有效点到左端点的括号数的最大值 (例:段 ))A)))(B(C(lc 值为 $7$,因为有效点 $C$ 到左端点的 (左边的) 括号数为 $7$);

rc (Right bracket count): 该区间内,有效点到右端点的括号数的最大值 (例:上面的段的 rc 值为 $6$,因为有效点 $A$ 到右端点的 (右边的) 括号数为 $6$);

ld (Left bracket difference): 该区间内,有效点到左端点的 [( 数 - ) 数] 的最大值 (例:上面的段的 ld 值为 $-2$,因为有效点 $A$ 到左端点有 $0$ 个 ( 和 $2$ 个 ));

rd (Right bracket difference): 该区间内,有效点到右端点的 [) 数 - ( 数] 的最大值 (例:上面的段的 rd 值为 $0$,因为有效点 $A$ 到右端点有 $3$ 个 ) 和 $3$ 个 ();

ans (Answer): 该区间内,两个有效点之间的括号数量的最大值 (记得是去掉匹配括号后的哦),也就是答案 (例:上面的段的 ans 值为 $5$,因为有效点 $A$ 和 $C$ 之间有 $5$ 个括号)。

(scx: 为什么要维护这些奇奇怪怪的信息呢?)

可以看出,这样在合并线段树节点的时候,带来了方便。先考虑叶节点,对于一个 (,显然它的值为 {l: 0, r: 1, lc: -∞, rc: -∞, ld: -∞, rd: -∞, ans: -∞};对于一个 ),它的值就是 {1, 0, -∞, -∞, -∞, -∞, -∞} (以下省略键);对于一个有效点,它的值应为 {0, 0, 0, 0, 0, 0, -∞};对于一个无效点,它的值就为 {0, 0, -∞, -∞, -∞, -∞, -∞}

考虑两个线段树节点 $x, y$ 的合并:($x$ 为左子树,假设 $x, y$ 内部所有匹配括号都是去掉的,但是合起来就不一定了)

l: 第一种情况,直接为 $x.l$;第二种情况,$x$ 的右端 ( 和 $y$ 的左端的部分 ) 抵消,还有多余的 ),这种情况为 $x.l - x.r + y.l$,最后只需取 $l = \max \{x.l, x.l - x.r + y.l\}$;(不论是否 $x.r > y.l$,反正取了 $\max$)

r: 完全对称地,$r = \max \{y.r, y.r - y.l + x.r\}$;

lc: 第一种情况,直接为 $x.lc$;第二种情况,$x$ 的右端 ( 和 $y$ 的左端的部分 ) 抵消,还有多余的 ),因此为 $x.l - x.r + y.lc$;第三种情况:若 $x$ 的右端 ( 与 $y$ 的左端 ) 抵消后,$x$ 的右端 ( 还有冗余,则为 $x.l + x.r + y.ld$,故 $lc = \max \{x.l, x.l - x.r + y.lc, x.l + x.r + y.ld\}$;

rc: 完全对称地,$rc = \max \{y.r, y.r - y.l + x.rc, y.r + y.l + x.rd\}$;

ld: 这个相对简单点。第一种情况,直接为 $x.ld$;第二种情况,由于无需考虑匹配的括号 (因为它抵消了),于是按照定义就是左端的 $x.r - x.l$ 加上右端的 $y.ld$,即 $ld = \max \{x.ld, x.r - x.l + y.ld\}$;

rd: 完全对称地,$rd = \max \{y.rd, y.l - y.r + x.rd\}$;

ans: 第一种情况,两端的 $x.ans, y.ans$;第二种情况,$x$ 右端 ( 多余,为 $x.rc + y.ld$;第三种情况,$y$ 左端 ) 多余,为 $x.rd + y.lc$,故 $ans = \max \{x.ans, y.ans, x.rc + y.ld, x.rd + y.lc \}$。

最后询问的时候比较简单,直接取根节点ans 值即可;修改亦不难,直接单点修改,一路 update() 上去。时间复杂度为 $O(N + Q \log N)$。

代码

#include <bits/stdc++.h>
#define N 100034
#define E 256101
#define INF 0xc0c0c0c0
using namespace std;

char op;
int n, q, e, remain;
int u, v, i;
int to[E], first[N], next[E];
int cnt = 0, id[N], val[N * 3];

inline int Max(const int x, const int y) {return x < y ? y : x;}
inline int Max(const int x, const int y, const int z) {return Max(Max(x, y), z);}

namespace ST{
    #define segc int M = L + R - 1 >> 1, lc = id << 1, rc = lc | 1

    struct node{
        /*
        l: ')', r: '(', lc, rc: '))...)((...(',
        ld: '((...(' - '))...)', rd: '))...)' - '((...('
        */
        int l, r, lc, rc, ld, rd, ans;
        node (int v = 0): l(0), r(0), lc(INF), rc(INF), ld(INF), rd(INF), ans(INF) {
            switch(v){
                case -1: r = 1; break;
                case -2: l = 1; break;
                case 1: lc = rc = ld = rd = 0; break;
            }
        }
    }x[N * 12];

    inline node merge(const node x, const node y){
        node ret = 0;
        ret.l = Max(x.l, x.l - x.r + y.l);
        ret.r = Max(y.r, y.r - y.l + x.r);
        ret.lc = Max(x.lc, x.l - x.r + y.lc, x.l + x.r + y.ld);
        ret.rc = Max(y.rc, y.r - y.l + x.rc, y.r + y.l + x.rd);
        ret.ld = Max(x.ld, y.ld + x.r - x.l);
        ret.rd = Max(y.rd, x.rd + y.l - y.r);
        ret.ans = Max(Max(x.ans, y.ans), x.rc + y.ld, x.rd + y.lc);
        return ret;
    }

    void build(int id, int L, int R){
        if(L == R) {x[id] = node(val[L]); return;}
        segc; build(lc, L, M); build(rc, M + 1, R);
        x[id] = merge(x[lc], x[rc]);
    }

    void adj(int id, int L, int R, int h){
        if(L == R) {x[id] = node(val[L]); return;}
        segc; h <= M ? adj(lc, L, M, h) : adj(rc, M + 1, R, h);
        x[id] = merge(x[lc], x[rc]);
    }
}

inline void addedge(int u, int v){
    to[++e] = v; next[e] = first[u]; first[u] = e;
    to[++e] = u; next[e] = first[v]; first[v] = e;
}

void dfs(int x, int px){
    int i, y;
    val[++cnt] = -1;
    val[++cnt] = 1; id[x] = cnt;
    for(i = first[x]; i; i = next[i])
        if((y = to[i]) != px)
            dfs(y, x);
    val[++cnt] = -2;
}

int main(){
    scanf("%d", &n);
    for(i = 1; i < n; ++i){
        scanf("%d%d", &u, &v);
        addedge(u, v);
    }
    remain = n; dfs(1, 0);
    ST::build(1, 1, cnt);
    for(scanf("%d", &q); q; --q)
        if(scanf("%1s", &op), op == 67){
            scanf("%d", &v);
            (val[v = id[v]] ^= 1) ? ++remain : --remain;
            ST::adj(1, 1, cnt, v);
        }else{
            if(remain <= 1) printf("%d\n", remain - 1);
            else printf("%d\n", ST::x[1].ans);
        }
    return 0;
}

注意到当匹配的括号去掉后,剩下的串的形式一定是 ))...)((...( 的形式,就可以用线段树 (大力分类讨论) 维护了。

坑1:注意 $l$ 是左边括号的个数,$r$ 是右边括号的个数,不要搞反了。