BZOJ1095【ZJOI2007】Hide捉迷藏 <括号序列+线段树>

Problem

【ZJOI2007】Hide捉迷藏

Time  Limit:  40  Sec\mathrm{Time\;Limit:\;40\;Sec}
Memory  Limit:  256  MB\mathrm{Memory\;Limit:\;256\;MB}

Description

捉迷藏Jiajia\mathrm{Jiajia}Wind\mathrm{Wind}是一对恩爱的夫妻,并且他们有很多孩子。某天,Jiajia\mathrm{Jiajia}Wind\mathrm{Wind}和孩子们决定在家里玩捉迷藏游戏。他们的家很大且构造很奇特,由NN个屋子和N1N-1条双向走廊组成,这N1N-1条走廊的分布使得任意两个屋子都互相可达。
游戏是这样进行的,孩子们负责躲藏,Jiajia\mathrm{Jiajia}负责找,而Wind\mathrm{Wind}负责操纵这NN个屋子的灯。在起初的时候,所有的灯都没有被打开。每一次,孩子们只会躲藏在没有开灯的房间中,但是为了增加刺激性,孩子们会要求打开某个房间的电灯或者关闭某个房间的电灯。为了评估某一次游戏的复杂性,Jiajia\mathrm{Jiajia}希望知道可能的最远的两个孩子的距离(即最远的两个关灯房间的距离)。
我们将以如下形式定义每一种操作:

  • C(hange)  i\mathrm{C(hange)}\;i :改变第ii个房间的照明状态,若原来打开,则关闭;若原来关闭,则打开。
  • G(ame)\mathrm{G(ame)} :开始一次游戏,查询最远的两个关灯房间的距离。

Input

第一行包含一个整数NN,表示房间的个数,房间将被编号为1,2,3N1,2,3\cdots N的整数。接下来N1N-1行每行两个整数a,ba,b,表示房间aa与房间bb之间有一条走廊相连。接下来一行包含一个整数QQ,表示操作次数。接着QQ行,每行一个操作,如上文所示。

Output

对于每一个操作G(ame)\mathrm{G(ame)},输出一个非负整数,表示最远的两个关灯房间的距离。
若只有一个房间是关着灯的,输出00;若所有房间的灯都开着,输出1-1

Sample Input

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
8
1 2
2 3
3 4
3 5
3 6
6 7
6 8
7
G
C 1
G
C 2
G
C 1
G

Sample Output

1
2
3
4
4
3
3
4

Hint

对于100%100\%的数据,N105,  M5×105N\le10^5,\;M\le5\times10^5

标签:括号序列 线段树

Solution

不会写动态点分,受小岛姐的启发写了线段树,虽然有77个标记,但推出公式还是比较好写的。

括号序列
对于一棵树,我们可以将其DFS\mathrm{DFS}序稍加优化,得到一个能记录每个点的子树结构的序列,即括号序列。
用左括号((表示进入某点的子树,用右括号))表示走出某点的子树。

对于此树,括号序列为(A(B)(C(D)(E)))(A(B)(C(D)(E))),可以看出括号序列有一个有用的性质,即仍两点之间的序列可以表示从前面的点走到后面的点的走法。例如对于点BBEE,其中间的序列为)(()()(()(,如果去掉中间匹配的括号则变为)(()((,将))定义为向上走,将((定义为向下走,那么可以看出BB向上走两步,再向下走两步即可到EE。那么如果维护此题中的树的括号序列,那么只需要知道任意两个黑点间的括号序列消除配对后的长度的最大值后即可得到答案。

线段树维护
对于一个序列的区间,可以用二元组(a,b)(a,b)表示其消去配对后的序列,即有aa个右括号和bb个左括号。
对于区间S1(a1,b1)S_1(a_1,b_1)S2(a2,b2)S_2(a_2,b_2),它们合并起来的区间是S(a,b)S(a,b),那么

  • b1>a2b_1>a_2时,S1S_1的左括号和S2S_2的右括号消去后一定会剩下b1a2b_1-a_2个左括号,因此a=a1b1+a2a=a_1-b_1+a_2b=b2b=b_2
  • b1a2b_1\le a_2时,S1S_1的左括号和S2S_2的右括号消去后一定会剩下a2b1a_2-b_1个右括号,因此a=a1a=a_1b=a2+b1+b2b=-a_2+b_1+b_2

那么易得到几个推论:

  • a+b=a1+a2+b1a2a+b=a_1+a_2+|b_1-a_2|
  • ab=a1+a2b1b2a-b=a_1+a_2-b_1-b_2
  • ba=a1a2+b1+b2b-a=-a_1-a_2+b_1+b_2

我们需要维护每个区间中两黑点间括号序列长度的最大值,那么我们还需要维护另外几个信息(有点像区间最大字段和):

  • lp=max{a+bS(a,b)S的一个前缀,且一个黑点接在S之后}lp=\max\lbrace a+b|S’(a,b)是S的一个前缀,且一个黑点接在S'之后\rbrace
  • lm=max{baS(a,b)S的一个前缀,且一个黑点接在S之后}lm=\max\lbrace b-a|S’(a,b)是S的一个前缀,且一个黑点接在S'之后\rbrace
  • rp=max{a+bS(a,b)S的一个后缀,且S接在一个黑点后}rp=\max\lbrace a+b|S'(a,b)是S的一个后缀,且S'接在一个黑点后\rbrace
  • rm=max{abS(a,b)S的一个后缀,且S接在一个黑点后}rm=\max\lbrace a-b|S'(a,b)是S的一个后缀,且S'接在一个黑点后\rbrace

对于一个区间SS,其两个子区间S1,S2S_1,S_2的七个值分别为a1,b1,lp1,lm1,rp1,rm1,dis1a_1,b_1,lp_1,lm_1,rp_1,rm_1,dis_1a2,b2,lp2,lm2,rp2,rm2,dis2a_2,b_2,lp_2,lm_2,rp_2,rm_2,dis_2,那么该区间的disdis一定是以下四个值的最大值:

  • dis1dis_1
  • dis2dis_2
  • rp1+lm2rp_1+lm_2
  • rm1+lp2rm_1+lp_2

而对于维护的44个信息,观察发现可以这样计算:

  • lp=max{lp1,  a1b1+lp2,  a1+b1+lm2}lp=\max\lbrace lp_1,\;a_1-b_1+lp_2,\;a_1+b_1+lm_2\rbrace
  • lm=max{lm1,  a1+b1+lm2}lm=\max\lbrace lm_1,\;-a_1+b_1+lm_2\rbrace
  • rp=max{rp2,  a2+b2+rp1,  a2+b2+rm1}rp=\max\lbrace rp_2,\;-a_2+b_2+rp_1,\;a_2+b_2+rm_1\rbrace
  • rm=max{rm2,  a2b2+rm1}rm=\max\lbrace rm_2,\;a_2-b_2+rm_1\rbrace

这样以后我们就可以用线段树维护这七个标记了。
除了updateupdate有点长以外,其他都和裸线段树一样。

以后找时间把动态点分学一学吧。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <bits/stdc++.h>
#define MAX_N 100000
#define INF 0x3f3f3f3f
#define mid ((s+t)>>1)
using namespace std;
template <class T> inline void read(T &x) {
x = 0; int c = getchar(), f = 1;
for (; !isdigit(c); c = getchar()) if (c == 45) f = -1;
for (; isdigit(c); c = getchar()) (x *= 10) += f*(c-'0');
}
vector <int> G[MAX_N+5];
int n, cnt, col[MAX_N+5];
int ind, seq[(MAX_N<<2)+5], dfn[MAX_N+5];
struct node {int a, b, lp, lm, rp, rm, dis;} tr[(MAX_N<<4)+5];
void addedge(int u, int v) {G[u].push_back(v), G[v].push_back(u);}
void DFS(int u, int f) {
seq[++ind] = -1, seq[++ind] = u, dfn[u] = ind;
for (int i = 0, v; i < (int)G[u].size(); i++)
if ((v = G[u][i]) ^ f) DFS(v, u);
seq[++ind] = -2;
}
void init(int v, int p) {
tr[v].a = tr[v].b = 0, tr[v].dis = -INF;
if (seq[p] == -1) tr[v].b = 1;
if (seq[p] == -2) tr[v].a = 1;
if (seq[p] > 0 && col[seq[p]])
tr[v].lp = tr[v].lm = tr[v].rp = tr[v].rm = 0;
else tr[v].lp = tr[v].lm = tr[v].rp = tr[v].rm = -INF;
}
void update(int v, int s, int t) {
int a = tr[v<<1].a, b = tr[v<<1].b;
int c = tr[v<<1|1].a, d = tr[v<<1|1].b;
tr[v].a = b < c ? a-b+c : a;
tr[v].b = b < c ? d : b-c+d;
tr[v].lp = tr[v<<1].lp, tr[v].rp = tr[v<<1|1].rp;
tr[v].lm = tr[v<<1].lm, tr[v].rm = tr[v<<1|1].rm;
tr[v].lp = max(tr[v].lp, tr[v<<1|1].lp+a-b);
tr[v].lp = max(tr[v].lp, tr[v<<1|1].lm+a+b);
tr[v].lm = max(tr[v].lm, tr[v<<1|1].lm-a+b);
tr[v].rp = max(tr[v].rp, tr[v<<1].rp-c+d);
tr[v].rp = max(tr[v].rp, tr[v<<1].rm+c+d);
tr[v].rm = max(tr[v].rm, tr[v<<1].rm+c-d);
tr[v].dis = max(tr[v<<1].dis, tr[v<<1|1].dis);
tr[v].dis = max(tr[v].dis, tr[v<<1|1].lm+tr[v<<1].rp);
tr[v].dis = max(tr[v].dis, tr[v<<1|1].lp+tr[v<<1].rm);
}
void build(int v, int s, int t) {
if (s == t) {init(v, s); return;}
build(v<<1, s, mid), build(v<<1|1, mid+1, t);
update(v, s, t);
}
void modify(int v, int s, int t, int p) {
if (s == t) {init(v, s); return;}
if (p <= mid) modify(v<<1, s, mid, p);
else modify(v<<1|1, mid+1, t, p);
update(v, s, t);
}
int main() {
read(n), cnt = n;
for (int i = 1; i <= n; i++) col[i] = 1;
for (int i = 1, u, v; i < n; i++)
read(u), read(v), addedge(u, v);
int T; read(T), DFS(1, 0), build(1, 1, ind);
while (T--) {
char opt[2]; scanf("%s", opt);
if (opt[0] == 'C') {
int x; read(x);
cnt += col[x] ? -1 : 1, col[x] ^= 1;
modify(1, 1, ind, dfn[x]);
}
if (opt[0] == 'G') {
if (!cnt) puts("-1");
else if (cnt == 1) puts("0");
else printf("%d\n", tr[1].dis);
}
}
return 0;
}