【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≤3∗105
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();
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 用 C# 插值字符串处理器写一个 sscanf
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!