[CSP-S 2022] 假期计划 题解
题面
题面冗长,不便展示,详见 洛谷。
考场想法
对于每一个点给他能到达的点都建一条边,最后跑一遍 DFS
。期望分数:\(60\)。
朴素想法
枚举所有可能的四个点,看是否能 “互通有无”,如果可以就更新最终答案。
时间复杂度:\(O(n^4)\)
期望分数:\(55\)。
正解思路
优化朴素想法,其实我们可以进行贪心考虑。
预处理
说在前面,可以预处理出来一个点是否能够到达另一个点,由于边权都是 \(1\),因此用 BFS
可实现,即从每个点开始遍历,能遍历到的点都记为可到达,时间复杂度:\(O(nm)\),可接受。
贪心
不妨设当前的路径是:\(1\rightarrow a\rightarrow b\rightarrow c\rightarrow d\rightarrow 1\)。
发现在考虑\(1\rightarrow a\rightarrow b\rightarrow c\rightarrow ?\rightarrow 1\) 的时候,可以贪心地找到 \(c\rightarrow 1\) 之间可行的点权最大的点,将其作为 \(d\)。
对应地,在考虑 \(1\rightarrow ?\rightarrow b\rightarrow c\rightarrow d\rightarrow 1\) 的时候,也可以这么贪心出 \(a\)。
于是想到,有没有可能只需要枚举 \(b\) 和 \(c\) 就可以了呢?答案是肯定的!(只不过需要判断一下 \(b\) 与 \(c\) 是否可达)
结合之前的想法,整个问题就基本解决了,让我们捋一下思路:
- 二重循环确定 \(b, c\);
- 找到 \(1\rightarrow b\) 与 \(c\rightarrow 1\) 之间的可行点权最大者,将其分别置为 \(a, d\);
- 输出答案,完美撒花~
噢噢,看起来这样就可以了呢,让我们来实现一下吧——
可以了吗?(贪心中的纰漏)
注意,实际操作的时候 \(a, b, c, d\) 这四个点是有可能重复的,这样就不行了。
哦我的老伙计,那我们该怎么办呢!
答案是:给 \(a, d\) 两个点存三个可能的备胎,这样即使发生:
\(a\) 与 \(c\) 重合了,换完了又与 \(d\) 重合了
的情况也不怕,换上第三个备胎就好了,对于 \(d\) 同理。
如果这样实现有些许麻烦,可以这么做:
枚举所有可能的备胎组合情况,取四个点都不重的情况中的点权和最大值。
这样省去了许多判断,更简洁优美。
形式化地说:预处理出这,么一个数组:\(choice[i][j](j\in\{0, 1, 2\})\),表示点 \(i\) 和点 \(1\) 同时可达的点中,点权第 \(j + 1\) 大的点。
需要注意,可能点 \(i\) 和点 \(1\) 同时可达的点不足三个,这时候 \(j\) 取不到 \(2\),可能产生越界,有很多方式避免他,这里我选择用 vector
类型存储 choice[][]
。
结合之前的想法,整个问题就正式解决了,让我们捋一下思路:
- 二重循环确定 \(b, c\);
- 枚举所有可能的 \(choice[b][]\) 与 \(choice[c][]\),作为 \(a, d\),判断四个点 \(a,b,c,d\) 是否重合,如果不重合就统计他的点权和并与当前答案比较;
- 输出答案,完美撒花~
参考代码
// Problem: P8817 [CSP-S 2022] 假期计划
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P8817
// Memory Limit: 512 MB
// Time Limit: 2000 ms
// Author: Moyou
// Copyright (c) 2022 Moyou All rights reserved.
// Date: 2022-12-18 17:33:37
#include <algorithm>
#include <cstring>
#include <iostream>
#include <queue>
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
const int N = 2510;
int score[N]; // 点权
bool pass[N][N]; // 记录两点是否可达
int n, m, k;
vector<int> g[N]; // 图
vector<int> choice[N]; // 备胎们
void init()
{
cin >> n >> m >> k;
for (int i = 2; i <= n; i++)
cin >> score[i];
for (int i = 1; i <= m; i++)
{
int a, b;
cin >> a >> b;
g[a].push_back(b);
g[b].push_back(a);
}
}
void bfs(int s)
{
int dist[N];
memset(dist, 0x3f, sizeof dist);
dist[s] = 0;
queue<int> q;
q.push(s);
while (q.size())
{
auto t = q.front();
q.pop();
if (s != t)
{
pass[s][t] = 1;
if (s != 1 && pass[1][t])
{
choice[s].push_back(t);
sort(choice[s].begin(), choice[s].end(),
[](int a, int b)
{
return score[a] > score[b];
});
if (choice[s].size() > 3) // 不可超了
choice[s].pop_back();
}
}
if (dist[t] == k + 1) // 超出可达的范围
continue;
for (auto i : g[t])
if (dist[i] > dist[t] + 1)
dist[i] = dist[t] + 1, q.push(i);
}
}
int mx = -INF;
bool diff(int a, int b, int c, int d)
{
return a != c && b != d && a != d;
}
signed main()
{
init();
for (int i = 1; i <= n; i++)
bfs(i);
for (int b = 2; b <= n; b++)
for (int c = 2; c <= n; c++)
if (pass[b][c] && b != c)
for (auto i : choice[b])
for (auto j : choice[c])
if (diff(i, b, c, j))
mx = max(mx, score[b] + score[c]
+ score[i] + score[j]);
cout << mx << endl;
return 0;
}