P8817
最初写于 2022 年 10 月,于 2026 年 10 月更新。删减了代码中无用的信息。
欢迎您到我的博客中查看本文,谢谢!
下文中,
注意,为方便,后文定义自己和自己不可达。
观察数据范围,
注意:对于满足边权全为
1 的图,单源最短路可以做到\mathcal{O}(n) 的复杂度(采用 bfs);对于满足边权只有
0 ,1 两种的图,单源最短路也可以做到\mathcal{O}(n) 的复杂度(采用 0-1 bfs)。
我们考虑一条
很直接的想法是,依次考虑
那么只能考虑在枚举四元对的基础上优化。复杂度最多
这个思想非常基础且常见,一种很常见的问题是低于
\mathcal{O}(n^2) 的复杂度解决二元对问题(如 P15451),或者低于\mathcal{O}(n^3) 、甚至低于\mathcal{O}(n^2) 的复杂度解决三元对问题(如 P8667)。此类问题的常见手段是:相比于朴素的暴力枚举二元对 / 三元对,我们只枚举二元对 / 三元对中的一元,而剩下的一元 / 二元利用预处理 / 记录前后缀最值 / 排序二分等思想,快速得到最合适的选择(如果是贪心最优化问题);或者快速得到合法选择的数量(如果是计数问题),从而优化复杂度。
对于此题,我们要求
\mathcal{O}(n^2) 解决四元对贪心问题,那么只能枚举两元,剩余的两元必须利用手段快速得到最合适的选择。
列举这个四元对的所有限制:
然后最优化
枚举哪两个点最合适呢?
- 如果我们枚举
a 和c ,b 存在两个制约:和a 可达且和c 可达,并且制约同时和两个变元有关,难以在优秀的复杂度下处理。 - 如果我们枚举
a 和d ,需要快速得到的b 和c 之间有制约:b 和c 需要可达。需要快速得到的点之间有制约,不好,难以处理。 - 如果我们枚举
b 和c ,需要快速得到的a 和d 之间没有制约!同时,与a 有关的制约只有和1 可达且与b 可达,但前者的1 是一个固定的点,所以变元只有1 个;与d 有关的制约同理。所以这是一个优秀的方案。
这里可能有人问:
a \ne d 难道不算a 和d 之间的制约吗?前面说过,互异性问题是一个比较好处理的制约,我们有比较成熟的处理手段,无需特别在意。同时,做过一些三元对问题的同学应该有一定的直觉:三元对问题中我们也通常枚举三元对中,中间的那个元,而不是枚举落在边缘的变元。比如长度为
3 的子序列问题我们通常枚举的是这个子序列中间的那个元素。一些题目:P8667、P11794。有这个直觉的同学也更容易想到本题应当枚举中间的两个变元。本题还有一个思考路径:思考这个题的弱化版本,如果要求的不是经过四个点,而是三个点或者两个点,应当怎么做?
根据这个思想,贪心思路比较容易想到。定义
这个定义是怎么来的?对着
a 和d 的制约条件写的。容易看出,在这个定义下,合适的a 应当取f(b) ;合适的d 应当取f(c) 。
第一步:我们预处理出
第二步:直接
现在来解决一直被我们忽略的互异性问题。比如:会出现
互异性问题的解决方案通常是考虑次大值:
更换定义,
当发现
当发现
发现并没有完全解决:如果这样处理后的
还是贪心思想,考虑把
重复性解决了,但是还有个细节:如果原先
至于原先
如果直接这么写是没有问题的,但是好多细节,太麻烦。下面是一种更好写的处理方法:
直接分别枚举
需要提前预处理出
细节:注意某些点可达,还与
如果枚举到某个
#include <bits/stdc++.h>
#define int long long
int read() {
int x = 0; bool f = true; char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = false;
for (; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
return f ? x : -x;
}
const int N = 2505;
typedef std::pair<int, int> pii;
std::vector<int> G[N], f[N];
// f[u] 存放:可达 u 且可达 1 的前三大 v
bool ok[N][N]; // u 和 v 是否可达?
int n, m, k, w[N], dis[N];
void bfs(int s) {
memset(dis, -1, sizeof(dis));
std::queue<int> q;
q.push(s); dis[s] = 0;
while (!q.empty()) {
int u = q.front(); q.pop();
if (u != s) {
ok[s][u] = true;
if (s != 1 && ok[1][u]) {
f[s].push_back(u);
std::sort(f[s].begin(), f[s].end(), [](int u, int v) {
return w[u] > w[v];
}); // 注意这里 sort 元素数量不超过 3,效率可看作常数
}
if (f[s].size() > 3) f[s].pop_back();
}
if (dis[u] == k + 1) continue; // dis == k + 1 时已经是合法距离最远的点了,不可再向外扩展
for (int v : G[u]) if (dis[v] == -1) {
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
signed main() {
n = read(); m = read(); k = read();
for (int u = 2; u <= n; ++u) w[u] = read();
while (m--) {
int u = read(), v = read();
G[u].push_back(v);
G[v].push_back(u);
}
for (int u = 1; u <= n; ++u) bfs(u);
int ans = 0;
for (int b = 2; b <= n; ++b)
for (int c = 2; c <= n; ++c) if (ok[b][c])
for (int a : f[b])
for (int d : f[c])
if (a != c && b != d && a != d)
// b != c 由 ok[b][c] 的定义保证;a != b,c != d 由 f 的定义保证
ans = std::max(ans, w[a] + w[b] + w[c] + w[d]);
printf("%lld\n", ans);
return 0;
}
如果觉得这篇题解写得好,请不要忘记点赞,谢谢!