先 BFS 求限制步数内可达,再为每个中间点保留高分候选并枚举中间两个景点。
OJ: luogu
题目 ID: P8817
难度:提高+/省选-
标签:图论BFS枚举贪心
日期: 2026-07-06 08:46
题意
从家 1 出发,依次游玩 4 个互不相同的景点 A,B,C,D,最后回到家。每一段行程都要求最多转车
每个景点有分数,要求最大化:
score[A] + score[B] + score[C] + score[D]思路
小数据可以直接枚举 4 个景点,再检查 5 段路是否都能在限制内到达:
// brute.cpp:小数据暴力解,枚举 4 个不同景点并检查 5 段行程是否可达。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 25;
int n, m, k;
long long score[MAXN];
vector<int> graph_edges[MAXN];
bool can_reach[MAXN][MAXN];
void bfs(int start) {
int dist[MAXN];
queue<int> q;
for (int i = 1; i <= n; i++) {
dist[i] = -1;
}
dist[start] = 0;
q.push(start);
while (!q.empty()) {
int u = q.front();
q.pop();
if (dist[u] > k + 1) {
continue;
}
can_reach[start][u] = true;
if (dist[u] == k + 1) {
continue;
}
for (int i = 0; i < (int)graph_edges[u].size(); i++) {
int v = graph_edges[u][i];
if (dist[v] == -1) {
dist[v] = dist[u] + 1;
q.push(v);
}
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> k;
for (int i = 2; i <= n; i++) {
cin >> score[i];
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
graph_edges[u].push_back(v);
graph_edges[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
bfs(i);
}
long long answer = 0;
for (int a = 2; a <= n; a++) {
for (int b = 2; b <= n; b++) {
for (int c = 2; c <= n; c++) {
for (int d = 2; d <= n; d++) {
if (a == b || a == c || a == d || b == c || b == d || c == d) {
continue;
}
if (can_reach[1][a] && can_reach[a][b] && can_reach[b][c] &&
can_reach[c][d] && can_reach[d][1]) {
answer = max(answer, score[a] + score[b] + score[c] + score[d]);
}
}
}
}
}
cout << answer << '\n';
return 0;
}暴力枚举是
先从每个点做一次 BFS,得到 can_reach[x][y],表示
路线是:
1 -> A -> B -> C -> D -> 1如果我们枚举中间两个景点 B,C,就只剩下两边要选:
A需要满足1 -> A可达,并且A -> B可达;D需要满足C -> D可达,并且D -> 1可达。
图是无向图,所以“能作为某个点 can_reach[1][p] 和 can_reach[p][x]。
于是可以对每个点 B,C 时,一个候选最多会因为等于 A/B/C/D 中已有的点而不能用;我们只需要避开常数个点,保留前 4 个足够找到最优可用候选。
最后枚举所有有 can_reach[B][C] 的 B,C,在 best[B] 中选 A,在 best[C] 中选 D,检查四个景点互不相同并更新答案。
代码
// main.cpp:BFS 预处理可达性,再枚举中间两个景点 B、C。
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2505;
int n, m, k;
long long score[MAXN];
vector<int> graph_edges[MAXN];
bool can_reach[MAXN][MAXN];
int best_node[MAXN][4]; // best_node[x] 保存能作为 x 前一个景点的高分候选
void bfs(int start) {
static int dist[MAXN];
queue<int> q;
for (int i = 1; i <= n; i++) {
dist[i] = -1;
}
dist[start] = 0;
q.push(start);
while (!q.empty()) {
int u = q.front();
q.pop();
if (dist[u] > k + 1) {
continue;
}
can_reach[start][u] = true;
if (dist[u] == k + 1) {
continue;
}
for (int i = 0; i < (int)graph_edges[u].size(); i++) {
int v = graph_edges[u][i];
if (dist[v] == -1) {
dist[v] = dist[u] + 1;
q.push(v);
}
}
}
}
void add_candidate(int x, int node) {
for (int i = 0; i < 4; i++) {
if (best_node[x][i] == node) {
return;
}
}
for (int i = 0; i < 4; i++) {
if (best_node[x][i] == 0 || score[node] > score[best_node[x][i]]) {
for (int j = 3; j > i; j--) {
best_node[x][j] = best_node[x][j - 1];
}
best_node[x][i] = node;
return;
}
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m >> k;
for (int i = 2; i <= n; i++) {
cin >> score[i];
}
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
graph_edges[u].push_back(v);
graph_edges[v].push_back(u);
}
for (int i = 1; i <= n; i++) {
bfs(i);
}
for (int x = 2; x <= n; x++) {
for (int a = 2; a <= n; a++) {
if (a != x && can_reach[1][a] && can_reach[a][x]) {
add_candidate(x, a);
}
}
}
long long answer = 0;
for (int b = 2; b <= n; b++) {
for (int c = 2; c <= n; c++) {
if (b == c || !can_reach[b][c]) {
continue;
}
for (int i = 0; i < 4; i++) {
int a = best_node[b][i];
if (a == 0 || a == b || a == c) {
continue;
}
for (int j = 0; j < 4; j++) {
int d = best_node[c][j];
if (d == 0 || d == a || d == b || d == c) {
continue;
}
answer = max(answer, score[a] + score[b] + score[c] + score[d]);
}
}
}
}
cout << answer << '\n';
return 0;
}复杂度
从每个点 BFS 一次,复杂度为 B,C 都是
空间复杂度为
总结
本题的关键拆法是:不要直接枚举 A,B,C,D 四个景点,而是先固定中间连接 B -> C,再从两侧各取一个高分候选。
BFS 负责把“最多转车