题解:P7353 [2020-2021 集训队作业] Tom & Jerry

Problem Link

思考 Tom 怎么获胜,有以下两种情况:

  1. Tom 不断限制 Jerry 的活动范围,直到困死。
  2. ~Tom 瞎走都可以赢~,有一个点能让 Tom 必胜。

对于(1),显然 Tom 需要不断走割点,由此想到圆方树。

这里有个圆方树

假设 Tom 在 a,Jerry 在 d,Jerry 能在 a 的子树里任意走,所以 Tom 需要让 a 能直接到达 bc,否则就输了。由此看出每当 Tom 在 x,Jerry 在 x 的子树 y 内时,x 必须直接到达 y 代表的点双连通分量中的任意点。此时 Tom 必胜。

对于(2),考虑能让 Tom 必胜的那个点 x,此时 Jerry 可以在任何地方(否则(1)就讨论过了)。Tom 必胜条件就是对于 x 的任何子树,(1)的条件都成立。

若(1)(2)都不成立,Tom 必败。

接下来实现挺复杂的,我看其他 dalao 题解是没怎么看懂(我太蒻了),这里介绍一下我的写法。

考虑如何表示 x 能直接到达其点双连通分量的任意点。可以将这个状态放到 x 到对应方点的边上。

枚举原图上的边,如 (a,b),则在圆方树上让 w(a,A)w(b,A) 各加一,这很好实现。a 能直接到 A 中任意点的充要条件是 w(a,A)=szA1szAA 代表的点双连通分量的大小)。

必胜条件可以转化为:对于一个根为 x 的子树中,所有 u 是圆点,v 是方点,depu<depv 的边 (u,v) (如下图红边)均合法。记不合法的边的数量fx,满足条件且不合法的的边称为不合法边。

容易得到转移:fysonxfx。对于不合法边 (u,v),为代码更好写,让 fvfv+1

考虑换根 dp,记 gx 为删去 x 的子树(除了 x)剩下的子树中不合法边的数量

转移为:gy=gx+fxfy+Δ

Δ 的取值有:

  • (x,y) 有原来是不合法边,Δ=1
  • (x,y) 现在是不合法边,Δ=1
  • (x,y) 啥都不是,Δ=0

最后用 lca 求出需要满足的子树 x,注意特判情况(2)。

默认 n,m,q 同级,时间复杂度为 O(nlogn)

update on 2024.10.3 修改代码中的部分错误

Code:

#include<iostream> #include<cstdlib> #include<ctime> #include<cassert> #include<vector> #include<cmath> #include<cstring> #include<set> #include<queue> #include<algorithm> using namespace std; const int N = 2e5 + 10, M = 2 * N; int dfn[N], low[N], st[N], sz[N], f[N], g[N], tot; int fa[N][21], dep[N], n, m, q, ifa[N]; vector <int> e[N]; int head[N], vi[M], ne[M], wi[M], htot; vector < pair <int, int> > vt; void addedge(int u, int v){ ne[++htot] = head[u], vi[htot] = v, head[u] = htot; } void add(int u, int v){ addedge(u, v); addedge(v, u); } void tarjin(int x){ dfn[x] = low[x] = ++dfn[0], st[++st[0]] = x; for(int y : e[x]){ if(!dfn[y]){ tarjin(y); low[x] = min(low[x], low[y]); if(low[y] == dfn[x]){ add(x, ++tot); sz[tot] = 2; while(st[st[0]] != y){ sz[tot]++; add(st[st[0]--], tot); } add(st[st[0]--], tot); } } else low[x] = min(low[x], dfn[y]); } } void ad(int u, int v){ while(u != v){ if(dep[u] < dep[v]) swap(u, v); wi[ifa[u]]++, u = fa[u][0]; } } void dfs1(int x, int pfa){ dep[x] = dep[pfa] + 1, fa[x][0] = pfa; for(int i = 1; i <= 20; i++) fa[x][i] = fa[fa[x][i - 1]][i - 1]; for(int i = head[x]; i; i = ne[i]){ int y = vi[i]; if(y == pfa) continue; ifa[y] = i; dfs1(y, x); } } void dfs2(int x, int pfa, int op){ f[x] = op; for(int i = head[x]; i; i = ne[i]){ int y = vi[i]; if(y == pfa) continue; dfs2(y, x, (x <= n && wi[i] != sz[y] - 1)); f[x] += f[y]; } } void dfs3(int x, int pfa){ for(int i = head[x]; i; i = ne[i]){ int y = vi[i]; if(y == pfa) continue; int t = g[x] + f[x] - f[y]; if(y <= n && wi[i] != sz[x] - 1) t++; if(x <= n && wi[i] != sz[y] - 1) t--; g[y] = t; dfs3(y, x); } } int lca(int x, int y){ if(dep[x] < dep[y]) swap(x, y); for(int i = 20; ~i; i--) if(dep[fa[x][i]] >= dep[y]) x = fa[x][i]; if(x == y) return x; for(int i = 20; ~i; i--) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; return fa[x][0]; } bool check(int x, int y){ if(lca(x, y) == x){ int z = y; for(int i = 20; ~i; i--) if(dep[fa[z][i]] > dep[x]) z = fa[z][i]; return f[z] == 0; } return g[x] == 0; } int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); cin >> n >> m >> q; for(int i = 1, u, v; i <= m; i++){ cin >> u >> v; vt.push_back(make_pair(u, v)); e[u].push_back(v); e[v].push_back(u); } tot = n; tarjin(1); dfs1(1, 0); for(auto i : vt) ad(i.first, i.second); dfs2(1, 0, 0); dfs3(1, 0); bool flag = 0; for(int i = 1; i <= n; i++) flag |= (f[i] + g[i] == 0); while(q--){ int a, b; cin >> a >> b; if(flag){ cout << "Yes\n"; continue; } cout << (check(a, b)? "Yes\n" : "No\n"); } }
posted @   louisliang  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示