题解 P7353【[2020-2021 集训队作业] Tom & Jerry】
题目描述
给定一张包含 \(n\) 个顶点和 \(m\) 条边的 无向连通图,Tom 和 Jerry 在图上进行了 \(q\) 次追逐游戏。
在第 \(i\) 次游戏中,Tom 一开始位于顶点 \(a_i\),而 Jerry 一开始位于顶点 \(b_i\)(双方任何时候都知道自己和对方的位置),追逐规则如下:
-
Jerry 和 Tom 交替行动,Jerry 先行动。
-
Jerry 每次行动可以通过无向图中的 任意多条边(可以选择不移动),但是在移动过程中不能经过 Tom 当前所在的结点,否则就会被抓住。
-
Tom 每次行动只能通过无向图中的 至多一条边(可以选择不移动)。
-
如果 Tom 在一次行动后到达了 Jerry 的位置,那么 Tom 胜利。
Tom 尽量想要胜利,而 Jerry 会尽量阻止 Tom 胜利。
现在你需要对于每一局游戏,求出 Tom 是否一定能在有限次行动内获胜。
对于 \(100\%\) 的数据,\(1\leq n,m,q\leq 10^5\),\(1\leq x,y,a,b\leq n\),\(a_i\ne b_i\)。
保证给出的无向图连通,且不含重边和自环。
solution
由于要求 Jerry 的路径不能与 Tom 的位置重叠,这里就蕴含了一种割点的想法,考虑建圆方树。
首先可以发现,如果一个圆点能一步到达它相邻某个方点的点双中所有点,Tom 一旦站上去这个点,如果 Jerry 逃不出这个点双,Jerry 就会被吃;如果没有这样的点,Jerry 一直待在这个点双里,Jerry 就赢了。可以发现这种点异常的关键,不如先取个名字,叫作一步杀点。
假如说 Jerry 待在一个点双里面,Tom 来的方向到达这个点双的第一个点无法一步杀,Jerry 要躲在 Tom 到的第一个点一步到不了的点。当 Tom 进入这个点双的时候,Jerry 就可以开始考虑他应该逃向何处。可以发现 Jerry 实际上整张图都可以去,分两种情况,如果要经过 Tom 所在的点,那么让 Tom 先走一步,然后 Jerry 逃走;如果不经过 Tom 所在点,Jerry 直接跑就可以了。
但是一直跑下去也不是一个办法,我们需要找一种稳定的策略。可以发现核心就在于 Tom 来的方向到达这个点双的第一个点无法一步杀,假如我们找到两个点双,假如它们对应的方点 \(x, y\) 之间的路径 \(x- u- \cdots- v- y\) 上,\(u\) 无法一步杀 \(x\),\(v\) 无法一步杀 \(y\),那么 Jerry 在这两个点双之间来回奔跑就行了。只要一开始 Jerry 到达这对点双的一端,Jerry 就赢了。
总结:如果一个圆点不能一步到达它相邻某个方点的点双中所有点,那么将这条边定向为圆点到方点。我们仔细分讨一下,就会发现:
- 如果有两个方点 \(x, y\),它们之间的路径形如 \(x- u- \cdots- v- y\),如果发现 \(u\to x\) 和 \(v\to y\) 这两条边被定向了,那么标记 \((x, y)\) 为好的点对。询问 \(a, b\) 时,删去 \(a\),若有一个好的点对的其中一端在 \(b\) 所在连通块中,那么 Jerry 就获胜了(Jerry 可以在这两个方点中来回横跳)。
- 还有一种情况,如果一个方点上没有点定向到它,而同时 Jerry 一开始可以到达这个方点,那么 Jerry 也获胜了。
可以证明这个和 ix35 的结论是等价的,过程就不写了。至此可以平方完成,然后就用其它技术例如树上倍增之类的优化一下就可以了,后面的部分一点都不难,就不写了。
code
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define debug(...) fprintf(stderr, ##__VA_ARGS__)
#else
#define endl "\n"
#define debug(...) void(0)
#endif
using LL = long long;
constexpr int N = 1e5 + 10;
int n, m, q, siz[N << 1];
bool upw[N << 1];
basic_string<int> g[N], tr[N << 1];
vector<pair<int, int>> E[N << 1];
int dfn[N << 1], tot, cnt;
void tarjan(int u) {/*{{{*/static int low[N], stk[N], top;static vector<pair<int, int>> Estk;low[u] = dfn[u] = ++cnt, stk[++top] = u;for (int v : g[u]) {auto e = make_pair(u, v);if (dfn[v]) {low[u] = min(low[u], dfn[v]);if (dfn[v] < dfn[u]) Estk.push_back(e);} else {Estk.push_back(e);tarjan(v), low[u] = min(low[u], low[v]);if (low[v] >= dfn[u]) {int p = ++tot;tr[p] += u, tr[u] += p;do tr[p] += stk[top], tr[stk[top]] += p; while (stk[top--] != v);auto lst = e;do E[p].push_back(lst = Estk.back()), Estk.pop_back(); while (lst != e);}}}
}/*}}}*/
auto getw(int u, int v) { return dfn[u] < dfn[v] ? upw[v] : upw[u]; }
int anc[18][N << 1], dep[N << 1];
void dfs1(int u, int fa) {anc[0][u] = fa;dfn[u] = ++cnt, siz[u] = 1, dep[u] = dep[fa] + 1;if (u > n) {auto bg = E[u].begin(), ed = E[u].end();for (auto& [x, y] : E[u]) if (x > y) swap(x, y);sort(bg, ed), E[u].erase(unique(bg, ed), ed);static int deg[N << 1];for (int v : tr[u]) deg[v] = 0;for (auto [x, y] : E[u]) deg[x] += 1, deg[y] += 1;for (int v : tr[u]) if (deg[v] != (int)tr[u].size() - 1) upw[v == fa ? u : v] = true;}for (int v : tr[u]) if (v != fa) dfs1(v, u), siz[u] += siz[v];
}
bool ok[N << 1];
vector<int> dfs2(int u, int fa) {bool done = true;vector<int> pre;for (int v : tr[u]) if (v != fa) {auto&& res = dfs2(v, u);if (v > n && getw(u, v)) res.push_back(v);if (!res.empty()) {if (done || !pre.empty()) {done = true;for (int x : pre) ok[x] = true;for (int x : res) ok[x] = true;pre.clear();}}}return pre;
}
int jump(int u, int k) {for (int j = 17; j >= 0; j--) if (k >> j & 1) u = anc[j][u];return u;
}
vector<int> vec;
bool cok(int l, int r) {auto it = lower_bound(vec.begin(), vec.end(), l);return it != vec.end() && *it <= r;
}
bool check(int x, int y) {if (dfn[x] <= dfn[y] && dfn[y] < dfn[x] + siz[x]) {y = jump(y, dep[y] - dep[x] - 1);return cok(dfn[y], dfn[y] + siz[y] - 1);} else {return cok(1, dfn[x] - 1) || cok(dfn[x] + siz[x], tot);}
}
int main() {
#ifndef LOCALcin.tie(nullptr)->sync_with_stdio(false);
#endifcin >> n >> m >> q, tot = n;for (int i = 1, u, v; i <= m; i++) cin >> u >> v, g[u] += v, g[v] += u;for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);cnt = 0, dfs1(1, 0);for (int i = n + 1; i <= tot; i++) {int cc = 0;for (int v : tr[i]) if (getw(i, v)) ++cc;if (cc == (int)tr[i].size()) ok[i] = true;}dfs2(1, 0);for (int i = 1; i <= tot; i++) if (ok[i]) vec.push_back(dfn[i]);sort(vec.begin(), vec.end());for (int j = 1; j <= 17; j++) {for (int i = 1; i <= tot; i++) anc[j][i] = anc[j - 1][anc[j - 1][i]];}while (q--) {int x, y;cin >> x >> y;cout << (!check(x, y) ? "Yes" : "No") << endl;}return 0;
}