Processing math: 100%

【Luogu P3950】部落冲突

Problem#

Description#

给出一棵树。

你需要处理下面三件事,所有的事件都是按照时间顺序给出的。

  1. Q,u,v 代表询问 u,v 之间能否相互到达

  2. C,u,v 代表 u,v 之间的边断开了

  3. U,x 代表第 UC 操作被还原

Input Format#

第一行两个数 nmn 代表了一共有 n 个部落,m 代表了以上三种事件发生的总数

接下来的 n1 行,每行两个数 p,q ,代表了第 p 个点与第 q 个点之间有一条道路相连

接下来的 m 行,每行表示一个操作,详见题目描述。

Output Format#

对于每个 Q 操作都给出一行 YesNo 代表询问结果。

Range#

n,m3105

Algorithm#

树状数组

Mentality#

其实本来是想练手一下 LCT 的,但是这题用 LCT 实在显得牛刀杀鸡 ……

大致看了一下题解,都用了树剖和 LCT 这样的,但其实有一种很简单的方法,只需要树状数组 + 随机数。

根据题目,对于一条路径 u,v ,这条路径联通当且仅当 u,v 上的边都没有被断开。

那么如何才能满足这个条件呢?换成对断边的要求,就是对于当前所有断开了的边,u,v 要么都在它的子树内,要么都不在。

则我们可以考虑用 dfn 序 + 树状数组维护子树信息,只要在每次断边的时候,给子树内每个点都赋上一个特有信息,然后询问的时候对 u,v 查询上面的信息是否相等即可。

如何赋上这个特殊信息呢?很简单,对于每次断边都 rand 一个特定权值,用树状数组给子树内的所有节点都加上这个权值即可。

查询的时候只需要查一下两点权值是否相同就行。

还原的时候再删掉就好了。

Code#

Copy
#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(); }
posted @   洛水·锦依卫  阅读(183)  评论(0编辑  收藏  举报
编辑推荐:
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· C# 深度学习:对抗生成网络(GAN)训练头像生成模型
阅读排行:
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· 趁着过年的时候手搓了一个低代码框架
· 用 C# 插值字符串处理器写一个 sscanf
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!
点击右上角即可分享
微信分享提示

目录

目录

×