题解 CF1873H【Mad City】
其他题解怎么又 Tarjan 又 Dijkstra 的,这是 div4H 的样子吗,来个简单好写的做法。
题面里的人名太复杂了,本题解中称为警察和小偷。
注意到,如果小偷成功到达了环上,那么一定不会被警察抓到。因为小偷知道警察下一步会走到哪里,他可以执行相同的操作(顺时针/逆时针/静止),使得他和警察之间的距离不变。
那么问题就是警察是否可以在小偷到达环上之前就蹲守在小偷的必经之路上,也就是环上离小偷最近的那个点。如果我们知道这个点,还知道任意两个点之间的距离,那么判断一下哪个更大就解决了。
下面是比较简单的实现方法。
维护一个并查集记录任意两个点是否连通,在加边的过程中,如果两端未连通,就加这条边,否则不加边并把两个端点记录为 。在处理完所有边后,我们得到了原基环树的一棵生成树,以及被删掉的那条环上的边的两端点。
通过树上倍增实现生成树的 LCA,然后使用树上差分可以求得生成树上任意两点间距离,设为 。考虑原基环树上两点间距离是什么,有两种情况,一种是只走生成树边,另一种是经过了被删除的边。前者即 ,后者根据经过方向不同又有两种情况,分别是 和 ,意思是 和 两条路径。因此,原基环树上两点间距离 。
标记环上的点的方法是求出 和 的 LCA,并暴力跳父亲标记 和 之间的所有点。求环上离小偷最近的点只需要枚举所有点 ,判断是否在环上,如果在环上就取出 最小的点,记作 。然后比较 和 的大小关系即可得到答案。
时间复杂度 。
// LUOGU_RID: 126418248
// Problem: H. Mad City
// Contest: Codeforces - Codeforces Round 898 (Div. 4)
// URL: https://codeforces.com/contest/1873/problem/H
// Memory Limit: 256 MB
// Time Limit: 4000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//By: OIer rui_er
#include <bits/stdc++.h>
#define rep(x,y,z) for(int x=(y);x<=(z);x++)
#define per(x,y,z) for(int x=(y);x>=(z);x--)
#define debug(format...) fprintf(stderr, format)
#define fileIO(s) do{freopen(s".in","r",stdin);freopen(s".out","w",stdout);}while(false)
using namespace std;
typedef long long ll;
mt19937 rnd(std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count());
int randint(int L, int R) {
uniform_int_distribution<int> dist(L, R);
return dist(rnd);
}
template<typename T> void chkmin(T& x, T y) {if(x > y) x = y;}
template<typename T> void chkmax(T& x, T y) {if(x < y) x = y;}
const int N = 2e5+5;
int T, n, a, b, ext_u, ext_v, dis[N], fa[19][N], cyc[N];
vector<int> e[N];
struct Dsu {
int fa[N];
void init(int x) {rep(i, 1, x) fa[i] = i;}
int find(int x) {return x == fa[x] ? x : fa[x] = find(fa[x]);}
bool same(int x, int y) {return find(x) == find(y);}
bool merge(int x, int y) {
if(same(x, y)) return false;
x = find(x); y = find(y);
fa[x] = y;
return true;
}
}dsu;
void dfs(int u, int f) {
dis[u] = dis[f] + 1;
fa[0][u] = f;
rep(i, 1, 18) fa[i][u] = fa[i-1][fa[i-1][u]];
for(int v : e[u]) if(v != f) dfs(v, u);
}
inline int LCA(int u, int v) {
if(dis[u] < dis[v]) swap(u, v);
per(i, 18, 0) if(dis[fa[i][u]] >= dis[v]) u = fa[i][u];
if(u == v) return u;
per(i, 18, 0) if(fa[i][u] != fa[i][v]) u = fa[i][u], v = fa[i][v];
return fa[0][u];
}
inline int disT(int u, int v) {
return dis[u] + dis[v] - 2 * dis[LCA(u, v)];
}
inline int disReal(int u, int v) {
return min({disT(u, v), disT(u, ext_u) + disT(ext_v, v) + 1, disT(u, ext_v) + disT(v, ext_u) + 1});
}
int main() {
for(scanf("%d", &T); T; T--) {
scanf("%d%d%d", &n, &a, &b);
dsu.init(n);
rep(i, 1, n) {
int u, v;
scanf("%d%d", &u, &v);
if(dsu.same(u, v)) {
ext_u = u;
ext_v = v;
}
else {
e[u].push_back(v);
e[v].push_back(u);
dsu.merge(u, v);
}
}
dfs(1, 0);
int p = LCA(ext_u, ext_v);
for(int u = ext_u; u != p; u = fa[0][u]) cyc[u] = 1;
for(int v = ext_v; v != p; v = fa[0][v]) cyc[v] = 1;
cyc[p] = 1;
int d = n, pos = 0;
rep(i, 1, n) if(cyc[i]) if(disReal(b, i) < d) d = disReal(b, i), pos = i;
puts(d < disReal(a, pos) ? "YES" : "NO");
// clear
rep(i, 1, n) {
dis[i] = cyc[i] = 0;
rep(j, 0, 18) fa[j][i] = 0;
e[i].clear();
}
ext_u = ext_v = 0;
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现