CodeForces 97 E. Leaders(点双连通分量 + 倍增)

题意

给你一个有 n 个点 m 条边的无向图,有 q 次询问,每次询问两个点 u,v 之间是否存在长度为奇数的简单路径。

1n,m,q105

题解

显然我们可以对于每个联通块单独处理,如果 u,v 不联通显然就不存在这条路。

然后对于每个联通块,首先随便弄一颗生成树。

  1. 如果这 uv 在树上的路径长就为奇数,显然是可以的,这个可以预处理深度就行了。
  2. 否则,uv 在树上的路径的边,只要存在一条边属于一个奇环就行了。

然后我们只要求出每条边是否属于一个奇环就行了。

单是求环的话,我们显然可以用求点双联通分量的方法来求的,求奇环需要用到下面的一个性质。

一个点双连通分量中要么每条边都在至少一个奇环上, 要么没有奇环.

这个证明是很显然的,可以自己手动画图理解。这个性质可以记住,到时候或许有用。

所以如果点双中存在一条边属于奇环,那么所有边都是在奇环上。

判断奇环可以直接判这个点连的两条边奇偶性是否相同,如果相同那么就是奇环了。


然后我们需要查询两个点的路径上是否存在一条边属于奇环,这个可以直接用树上差分做。

然后对于之前记一条边是否在奇环上,需要把这个点双中除了点双中最上面的那个点 u 全部标成 1

也就是我们都标到儿子上就行了。

实现起来就是 (cnt[u] + cnt[v] - 2 * cnt[Get_Lca(u, v)]) > 0cnt 为到根路径上标号的前缀和,用倍增求 Lca 就行了。

总结

多找性质,多思考,多记性质。

代码

具体实现看代码就行啦qwq 注意此处点双要存边才行,还要注意一些小细节。

#include <bits/stdc++.h> #define For(i, l, r) for(register int i = (l), i##end = (int)(r); i <= i##end; ++i) #define Fordown(i, r, l) for(register int i = (r), i##end = (int)(l); i >= i##end; --i) #define Set(a, v) memset(a, v, sizeof(a)) #define Cpy(a, b) memcpy(a, b, sizeof(a)) #define debug(x) cout << #x << ": " << x << endl #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define fir first #define sec second #define mp make_pair using namespace std; typedef pair<int, int> PII; inline bool chkmin(int &a, int b) {return b < a ? a = b, 1 : 0;} inline bool chkmax(int &a, int b) {return b > a ? a = b, 1 : 0;} inline int read() { int x = 0, fh = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') fh = -1; for (; isdigit(ch); ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48); return x * fh; } void File() { #ifdef zjp_shadow freopen ("E.in", "r", stdin); freopen ("E.out", "w", stdout); #endif } const int N = 1e5 + 1e3, M = N << 1; int n, m, Logn; int anc[N][20], dep[N]; vector<int> G[N]; bitset<N> vis1, vis2; int id[N], version; void Dfs_Init(int u, int fa = 0) { id[u] = version; vis1[u] = true; dep[u] = dep[anc[u][0] = fa] + 1; For (i, 1, Logn) anc[u][i] = anc[anc[u][i - 1]][i - 1]; for (int v : G[u]) if (!vis1[v]) Dfs_Init(v, u); } int dfn[N], lowlink[N], cnt[N]; PII sta[M]; int top = 0; void Tarjan(int u, int fa = 0) { static int clk = 0; dfn[u] = lowlink[u] = ++ clk; for (int v : G[u]) if (v != fa && dfn[v] < dfn[u]) { sta[++ top] = mp(u, v); if (!dfn[v]) { Tarjan(v, u); chkmin(lowlink[u], lowlink[v]); if (lowlink[v] >= dfn[u]) { int tmp = top, x, y, flag = 0; do { x = sta[top].fir; y = sta[top --].sec; if ((dep[x] & 1) == (dep[y] & 1)) { flag = 1; break; } } while (!(x == u && y == v)); if (!flag) continue ; top = tmp; do { x = sta[top].fir; y = sta[top --].sec; cnt[x] = cnt[y] = 1; } while (!(x == u && y == v)); cnt[u] = 0; } } else chkmin(lowlink[u], dfn[v]); } } void Dfs_Calc(int u, int fa = 0) { cnt[u] += cnt[fa]; vis2[u] = true; for (int v : G[u]) if (!vis2[v]) Dfs_Calc(v, u); } inline int Get_Lca(int x, int y) { if (dep[x] < dep[y]) swap(x, y); Fordown (i, Logn, 0) if (dep[anc[x][i]] >= dep[y]) x = anc[x][i]; if (x == y) return x; Fordown (i, Logn, 0) if (anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i]; return anc[x][0]; } inline bool Check(int u, int v) { if (id[u] != id[v]) return false; if ((dep[u] & 1) ^ (dep[v] & 1)) return true; return (cnt[u] + cnt[v] - 2 * cnt[Get_Lca(u, v)]) > 0; } int main () { File(); n = read(); m = read(); Logn = ceil(log2(n)); For (i, 1, m) { int u = read(), v = read(); G[u].push_back(v); G[v].push_back(u); } For (i, 1, n) if (!dfn[i]) ++ version, Dfs_Init(i), Tarjan(i), Dfs_Calc(i); int q = read(); For (i, 1, q) { int u = read(), v = read(); puts(Check(u, v) ? "Yes" : "No"); } return 0; }

__EOF__

本文作者zjp_shadow
本文链接https://www.cnblogs.com/zjp-shadow/p/9550817.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zjp_shadow  阅读(582)  评论(0编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示