【Luogu P3950】部落冲突
Problem
Description
给出一棵树。
你需要处理下面三件事,所有的事件都是按照时间顺序给出的。
-
\(Q, u, v\) 代表询问 \(u, v\) 之间能否相互到达
-
\(C, u, v\) 代表 \(u, v\) 之间的边断开了
-
\(U, x\) 代表第 \(U\) 次 \(C\) 操作被还原
Input Format
第一行两个数 \(n\) 和 \(m\) , \(n\) 代表了一共有 \(n\) 个部落,\(m\) 代表了以上三种事件发生的总数
接下来的 \(n - 1\) 行,每行两个数 \(p, q\) ,代表了第 \(p\) 个点与第 \(q\) 个点之间有一条道路相连
接下来的 \(m\) 行,每行表示一个操作,详见题目描述。
Output Format
对于每个 \(Q\) 操作都给出一行 Yes
或 No
代表询问结果。
Range
\(n, m\le 3*10^5\)
Algorithm
树状数组
Mentality
其实本来是想练手一下 \(LCT\) 的,但是这题用 \(LCT\) 实在显得牛刀杀鸡 ……
大致看了一下题解,都用了树剖和 \(LCT\) 这样的,但其实有一种很简单的方法,只需要树状数组 + 随机数。
根据题目,对于一条路径 \(u, v\) ,这条路径联通当且仅当 \(u, v\) 上的边都没有被断开。
那么如何才能满足这个条件呢?换成对断边的要求,就是对于当前所有断开了的边,\(u, v\) 要么都在它的子树内,要么都不在。
则我们可以考虑用 \(dfn\) 序 + 树状数组维护子树信息,只要在每次断边的时候,给子树内每个点都赋上一个特有信息,然后询问的时候对 \(u, v\) 查询上面的信息是否相等即可。
如何赋上这个特殊信息呢?很简单,对于每次断边都 \(rand\) 一个特定权值,用树状数组给子树内的所有节点都加上这个权值即可。
查询的时候只需要查一下两点权值是否相同就行。
还原的时候再删掉就好了。
Code
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iostream>
using namespace std;
#define LL long long
#define go(G, x, i, v) \
for (int i = G.hd[x], v = G.to[i]; i; v = G.to[i = G.nx[i]])
#define inline __inline__ __attribute__((always_inline))
inline LL read() {
LL x = 0, w = 1;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') w = -1;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 3) + (x << 1) + ch - '0';
ch = getchar();
}
return x * w;
}
const int Max_n = 3e5 + 5, mod = 998244353;
int n, m;
int cntd, dep[Max_n], dfn[Max_n], siz[Max_n];
int c[Max_n];
struct graph {
int hd[Max_n];
int cntr, nx[Max_n << 1], to[Max_n << 1];
void addr(int u, int v) {
cntr++;
nx[cntr] = hd[u], to[cntr] = v;
hd[u] = cntr;
}
} G;
struct que {
int u, v, x;
} k[Max_n];
namespace Input {
void main() {
n = read(), m = read();
int u, v;
for (int i = 1; i < n; i++) {
u = read(), v = read();
G.addr(u, v), G.addr(v, u);
}
}
} // namespace Input
namespace Init {
void build(int x, int fa) {
dep[x] = dep[fa] + 1, siz[x] = 1, dfn[x] = ++cntd;
go(G, x, i, v) if (v != fa) build(v, x), siz[x] += siz[v];
}
void main() { build(1, 0); }
} // namespace Init
namespace Solve {
void add(int k, int x) {
for (int i = k; i <= n; i += i & -i) (c[i] += x) %= mod;
}
int query(int k) {
int ans = 0;
for (int i = k; i; i -= i & -i) (ans += c[i]) %= mod;
return (ans + mod) % mod;
}
void main() {
srand((unsigned)time(NULL));
int cnt = 0, u, v;
char op;
for (int i = 1; i <= m; i++) {
scanf(" %c", &op);
u = read();
if (op == 'U')
add(dfn[k[u].v], -k[u].x), add(dfn[k[u].v] + siz[k[u].v], k[u].x);
else {
v = read();
if (op == 'Q')
printf("%s\n", query(dfn[u]) == query(dfn[v]) ? "Yes" : "No");
else {
k[++cnt].x = rand() % mod;
if (dep[u] > dep[v]) swap(u, v);
add(dfn[v], k[cnt].x), add(dfn[v] + siz[v], -k[cnt].x);
k[cnt].u = u, k[cnt].v = v;
}
}
}
}
} // namespace Solve
int main() {
#ifndef ONLINE_JUDGE
freopen("3950.in", "r", stdin);
freopen("3950.out", "w", stdout);
#endif
Input::main();
Init::main();
Solve::main();
}