把问题转成从 1 到 a 是否存在长度恰好为 L 的游走,用奇偶分层图 BFS 求最短同奇偶步数。
OJ: luogu
题目 ID: P5663
难度:普及+/提高
标签:图论bfs最短路
日期: 2026-06-19 19:38
题意
给出一个无向图。对每张工单 (a, L),问 1 号工人会不会在“第 L 层传播”里被卷入,从而需要给别人提供原材料。
思路
最直接的办法是按层数逐层扩展,做一个“恰好走 step 步能到哪些点”的 DP。
先看一个可以直接验证想法的朴素解:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, q;
cin >> n >> m >> q;
vector<vector<int>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<pair<int, int>> query(q);
int maxL = 0;
for (int i = 0; i < q; ++i) {
cin >> query[i].first >> query[i].second;
maxL = max(maxL, query[i].second);
}
vector<vector<int>> can(maxL + 1, vector<int>(n + 1, 0));
can[0][1] = 1;
for (int step = 1; step <= maxL; ++step) {
for (int u = 1; u <= n; ++u) {
if (!can[step - 1][u]) {
continue;
}
for (int v : g[u]) {
can[step][v] = 1;
}
}
}
for (auto [a, L] : query) {
cout << (can[L][a] ? "Yes" : "No") << '\n';
}
return 0;
}下面是另一种「状态搜索」风格的暴力写法。它把状态写成“已经走了几步、当前在哪个点”,再递归枚举下一步走向哪个相邻点:
另一种暴力写法:状态搜索
// brute_01_style.cpp:状态搜索风格暴力,用递归枚举每一步走向哪个相邻点。
#include <bits/stdc++.h>
using namespace std;
int n, m, q;
int max_l;
vector<vector<int> > g;
vector<pair<int, int> > query_list;
vector<vector<unsigned char> > can_reach; // can_reach[step][u] 表示恰好 step 步能到 u
vector<vector<unsigned char> > expanded; // expanded[step][u] 表示这个状态已经向后扩展过
void dfs_walk(int step, int u) {
can_reach[step][u] = 1;
if (step == max_l) {
return;
}
if (expanded[step][u]) {
return;
}
expanded[step][u] = 1;
// 下一步可以选择走向任意一个相邻工人。
for (int i = 0; i < (int)g[u].size(); i++) {
int v = g[u][i];
dfs_walk(step + 1, v);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> q;
g.assign(n + 1, vector<int>());
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
query_list.resize(q);
max_l = 0;
for (int i = 0; i < q; i++) {
int a, l;
cin >> a >> l;
query_list[i] = make_pair(a, l);
max_l = max(max_l, l);
}
can_reach.assign(max_l + 1, vector<unsigned char>(n + 1, 0));
expanded.assign(max_l + 1, vector<unsigned char>(n + 1, 0));
dfs_walk(0, 1);
for (int i = 0; i < q; i++) {
int a = query_list[i].first;
int l = query_list[i].second;
if (can_reach[l][a]) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
return 0;
}brute.cpp 用 can[step][u] 直接表示恰好 step 步能否到点 u,适合小数据对拍,但正式数据里 L 可达 1e9,显然不能这样推。
关键观察是:工单 (a, L) 等价于问“从 1 到 a 是否存在长度恰好为 L 的游走”。在无向图中,只要某种奇偶性的路径存在,就可以通过沿一条边来回走,把长度每次多补 2。
因此只需要知道:
- 到每个点的最短偶数步长度
- 到每个点的最短奇数步长度
这张图展示样例 1 的链结构:
graph G {
1 -- 2;
2 -- 3;
}
从这张图可以看出:
- 到
1的偶数步最短是0 - 到
2的奇数步最短是1 - 到
3的偶数步最短是2
一旦这些最短同奇偶步数知道了,查询 (a, L) 时,只要 dist[a][L mod 2] <= L,就说明可以先走最短那条同奇偶路径,再通过“来回两步”把长度补到正好 L。
只有 L 为偶数时要额外小心:dist[1][0] = 0 只是空路径,不能算作真的发生了一次传播。因此这里必须要求存在正长度偶数游走;无向图里只要 1 号点有邻边,最短这种游走就是 2。
实现时,把每个点拆成两个状态 (u,0) 和 (u,1),表示走到 u 时步数奇偶为 (1,0) 出发做 BFS,每经过一条边就翻转奇偶。
代码
#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, q;
cin >> n >> m >> q;
vector<vector<int>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
vector<array<int, 2>> dist(n + 1, {INF, INF});
queue<pair<int, int>> qu;
dist[1][0] = 0;
qu.push({1, 0});
// 分层图 BFS:状态 (u, p) 表示到点 u、步数奇偶为 p 的最短长度。
while (!qu.empty()) {
auto [u, p] = qu.front();
qu.pop();
for (int v : g[u]) {
int np = p ^ 1;
if (dist[v][np] != INF) {
continue;
}
dist[v][np] = dist[u][p] + 1;
qu.push({v, np});
}
}
int min_even_self = (g[1].empty() ? INF : 2);
while (q--) {
int a, L;
cin >> a >> L;
int p = L & 1;
int need = dist[a][p];
if (a == 1 && p == 0) {
need = min_even_self;
}
if (need <= L) {
cout << "Yes\n";
} else {
cout << "No\n";
}
}
return 0;
}复杂度
分层图只有 2n 个状态,BFS 是
总结
这题的关键不是按层硬模拟,而是把问题转成“同奇偶最短游走”。抓住“无向图里可以随时补两步”这个性质后,答案就只剩一次奇偶分层 BFS。
一图流解析
这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。


