0%

共价大爷游长沙

先膜毛爷爷。

题目描述

UOJ#207. 共价大爷游长沙

给出一棵动态的树,有一个集合 \(S\),每次询问集合中的所有点相连的路径是否一定经过 \((x, y)\)

四种操作: - \(\text{opt} = 1, x, y, u, v\):删除边 \((x, y)\),增加边 \((u, v)\); - \(\text{opt} = 2, x, y\):在集合 \(S\) 中加入 \((x, y)\); - \(\text{opt} = 3, x\):删除第 \(x\) 个加入集合 \(S\) 的点对; - \(\text{opt} = 4, x, y\):询问集合中的所有点相连的路径是否一定经过 \((x, y)\)

测试点编号 \(n\) \(m\) \(\text{type}\) 限制
1 \(n \le 100\) \(m \le 100\) 1, 2, 3, 4
2, 3 \(n \le 100000\) \(m \le 300000\) 2, 4
4, 5 2, 3, 4
6, 7 1, 2, 3, 4 \(\mid S\mid \le 10\)
8, 9, 10 1, 2, 3, 4

题解

对于测试点 1,可以暴力。可以用一个 set 维护边的信息,对于加边删边直接用 set 的 insert 和 erase,每次暴力遍历 s 中的所有点对走一遍看是否经过边 \((x, y)\)

对于测试点 2、3、4、5,因为没有加边删边操作,所以每次将加入点对 \((x, y)\) 这条链上的值 \(+1\),每次查询时判断 \((x, y)\) 上的和是否为 \(\mid S \mid\),正确性显然。因为不改变树的形态,而且维护的是一条链的信息,所以树链剖分和 LCT 应该都可以。

对于测试点 6、7,可以用 LCT,因为 \(\mid S \mid \le 10\),所以在删边的时候可以暴力枚举 \(10\) 种的变化。

暂时只会 \(70\) 分吧。

哦我好像想到正解了 (Update 2020.7.17 20点左右)。

考虑当 \(u\) 为根的时候,如果所有路径都经过 \((u, v)\),那么对于 \(S\) 中的所有点对 \((x, y)\),其路径有且仅有一个端点在以 \(x\) 为根 \(y\) 的子树中。

那么如何判断?

假设现在有一种数据类型 \(\text{Type}\),它支持一个操作 \(\text{operator}\) 满足 \(x \ \ \text{operator} \ \ x = 0\)\(0 \ \ \text{operator} \ \ x = x\),且有一个 \(\text{Type} \ \ ans\),那么在加进边的时候,把 \(ans \ \ \text{operator}\ \ rnd()\),删边时 \(\text{operator}\) 回去。那么可以记录整棵树的 \(ans\),每次询问以 \(x\) 为根 \(y\) 的子树的值,如果满足所有点对 \((u, v)\) 都经过 \((x, y)\) 这条边,那么该值应该等于 \(ans\)

我选择用了异或,如果加减法混合用应该也可以。

维护子树信息,Top Tree

LCT 维护的是实链的信息,对于除了 Access 和 Link 的操作并不会改变虚边的信息。

如果只考虑实链,那么 PushUp 是这样写:

1
void PushUp(int x) { sze[x] = sze[ch[x][0]] ^ sze[ch[x][1]] ^ v[x]; }

然后考虑虚链。

\(sze_x\) 表示 \(x\) 的子树虚链信息,\(v_x\) 表示 \(x\) 的信息。

  • Access(x) 操作:断 \(ch_{x, 1}\),连 \(i\)

    1
    2
    3
    4
    5
    6
    7
    void Access(int x) {
    for (int i = 0; x; i = x, x = fa[x]) {
    Splay(x);
    v[x] ^= sze[ch[x][1]], v[x] ^= sze[ch[x][1] = i];
    PushUp(x);
    }
    }

  • Link(x, y) 操作:连 \(y\)

    1
    void Link(int x, int y) { Split(x, y), fa[x] = y, v[y] ^= sze[x], PushUp(y); }

挺显然的

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <algorithm>
#include <cstdio>
#include <ctime>
#include <random>
#include <unordered_set>
#include <vector>

const int N = 3e5 + 10;

std::mt19937 rnd(time(NULL));

struct LinkCutTree {
int ch[N][2], fa[N], rev[N], sze[N], v[N];
int stk[N];

void PushUp(int x) { sze[x] = sze[ch[x][0]] ^ sze[ch[x][1]] ^ v[x]; }

void Reverse(int x) { std::swap(ch[x][0], ch[x][1]), rev[x] ^= 1; }

void PushDown(int x) {
if (rev[x]) {
if (ch[x][0])
Reverse(ch[x][0]);
if (ch[x][1])
Reverse(ch[x][1]);
rev[x] ^= 1;
}
}

bool IsRoot(int x) { return ch[fa[x]][0] != x && ch[fa[x]][1] != x; }

bool Child(int x) { return x == ch[fa[x]][1]; }

void Rotate(int x) {
int y = fa[x], z = fa[y], c = Child(x);
if (!IsRoot(y)) {
if (ch[z][0] == y)
ch[z][0] = x;
else
ch[z][1] = x;
}
fa[x] = z, fa[y] = x, fa[ch[x][c ^ 1]] = y;
ch[y][c] = ch[x][c ^ 1], ch[x][c ^ 1] = y;
PushUp(y);
}

void Splay(int x) {
int top = 0;
stk[++top] = x;
for (int i = x; !IsRoot(i); i = fa[i])
stk[++top] = fa[i];
while (top)
PushDown(stk[top--]);
while (!IsRoot(x)) {
int y = fa[x];
if (!IsRoot(y)) {
if (Child(x) ^ Child(y))
Rotate(x);
else
Rotate(y);
}
Rotate(x);
}
PushUp(x);
}

void Access(int x) {
for (int i = 0; x; i = x, x = fa[x]) {
Splay(x);
v[x] ^= sze[ch[x][1]], v[x] ^= sze[ch[x][1] = i];
PushUp(x);
}
}

void MakeRoot(int x) { Access(x), Splay(x), Reverse(x); }

void Split(int x, int y) { MakeRoot(x), Access(y), Splay(y); }

void Link(int x, int y) { Split(x, y), fa[x] = y, v[y] ^= sze[x], PushUp(y); }

void Cut(int x, int y) {
Split(x, y);
ch[y][0] = fa[x] = 0;
PushUp(y);
}

int Query(int x, int y) {
Split(x, y);
return v[y];
}

void Update(int x, int y) {
Access(x), Splay(x);
v[x] ^= y;
PushUp(x);
}
} lct;

struct Edge {
int u, v, w;

Edge() : u(0), v(0), w(0) {}
Edge(int _u, int _v, int _w) : u(_u), v(_v), w(_w) {}
} e[N];
int edge_cnt;

int main() {
int id;
scanf("%d", &id);
int n, m;
scanf("%d%d", &n, &m);
for (int i = 2, u, v; i <= n; ++i) {
scanf("%d%d", &u, &v);
lct.Link(u, v);
}
int ans = 0;
while (m--) {
int opt, x, y, u, v;
scanf("%d", &opt);
if (opt == 1) {
scanf("%d%d%d%d", &x, &y, &u, &v);
lct.Cut(x, y), lct.Link(u, v);
} else if (opt == 2) {
scanf("%d%d", &x, &y);
int z = rnd() % (1 << 30);
e[++edge_cnt] = Edge(x, y, z);
lct.Update(x, z), lct.Update(y, z);
ans ^= z;
} else if (opt == 3) {
scanf("%d", &x);
lct.Update(e[x].u, e[x].w), lct.Update(e[x].v, e[x].w);
ans ^= e[x].w;
} else {
scanf("%d%d", &x, &y);
printf("%s\n", lct.Query(x, y) == ans ? "YES" : "NO");
}
}
return 0;
}