4 月 27 日测试题解
最短路专场
T1 \({\color{green}{\text{100pts}}}\text{/100pts}\)
题意
给出 \(m\) 个变量与 \(n\) 个约束,每个约束形如以下三种中的一种;
- \(x_i - x_j \le w\)
- \(x_i - x_j \ge w\)
- \(x_i - x_j = w\)
有 \(q\) 个询问,每个询问为形如 \((x_i, x_j)\) 的二元组,要求 \(\max(x_i - x_j)\) 的值,当可以取到正无穷时,输出 \(\texttt{Infinity}\)。
对于 \(30\%\) 的数据,\(n, m \le 5\);
对于 \(100\%\) 的数据,\(m \le 200\),\(n \le 10^4\),\(q \le 10^6\),\(|w| \le 10^4\)。
思路
\(\text{100pts}\)
这道题是差分约束的裸题。对于每个约束,我们通过手段将其归约到同一种类型,即形如 \(x \le y + C\) 的形式,然后联想到 SSSP 中松弛操作的数学基础:三角不等式。对于一条边 \(e = (u, v, w)\),我们有 \(dis_v \le dis_u + w\)。
我们可以考虑建出图,对于每个形如上边一般形式的约束,建一条 \(y \to x\),权值为 \(C\) 的边,然后在上边跑最短路求出一组合法解。但此时,由于我们求的是最短路,得到的解事实上是满足约束条件的最大的一组解,于是满足了题目中的所有条件。
对于题目中的三种约束,我们有:
- \(x_i - x_j \le w \Leftrightarrow x_i \le x_j + w\)
- \(x_i - x_j \ge w \Leftrightarrow x_j \le x_i + (-w)\)
- \(x_i - x_j = w \Leftrightarrow x_i \le x_j + w, x_j \le x_i + (-w)\)
题目中保证给出的约束条件不会相互冲突,于是出现可以取 \(+\infty\) 的情况只可能是题目中的第二种约束或两变量间没有约束的情况,特判一下即可。
可以添加一个超级源来跑,不过这道题要求的值较为特殊,对于一组询问 \((x_i, x_j)\),其答案应为以 \(x_j\) 为源点的 \(dis_{x_i}\) 值。
代码
\(\text{100pts}\)
#include <bits/stdc++.h>
using i64 = long long;
struct Graph {
static constexpr i64 INF = 1e18;
struct Edge {
int u, v;
i64 w;
Edge(int _u, int _v, i64 _w) : u(_u), v(_v), w(_w) {}
};
int n;
std::vector<Edge> edges;
std::vector<std::vector<int>> e;
Graph(int _n) : n(_n + 1) {
e.resize(n);
return;
}
void insert(int u, int v, int w) {
edges.emplace_back(u, v, w);
e[u].push_back(edges.size() - 1);
return;
}
void add(int u, int v, int w) {
insert(u, v, w), insert(v, u, w);
return;
}
std::vector<i64> spfa(int s) {
std::vector<i64> dis(n, INF);
std::vector<bool> vis(n, false);
std::queue<int> q;
dis[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front();
vis[u] = false;
q.pop();
for (auto i : e[u]) {
int v = edges[i].v, w = edges[i].w;
if (dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
if (!vis[v]) {
vis[v] = true, q.push(v);
}
}
}
}
return dis;
}
};
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(0);
constexpr i64 INF = 1e18;
int n, m;
std::cin >> n >> m;
Graph g(n);
for (int i = 0; i < m; i++) {
int opt, u, v, w;
std::cin >> opt >> u >> v >> w;
if (opt == 0) {
g.insert(v, u, w);
} else if (opt == 1) {
g.insert(u, v, -w);
} else {
g.insert(v, u, w);
g.insert(u, v, -w);
}
}
int q;
std::cin >> q;
std::vector<std::vector<i64>> dis(n + 1);
for (int i = 1; i <= n; i++) {
dis[i] = g.spfa(i);
}
while (q--) {
int u, v;
std::cin >> u >> v;
if (dis[v][u] == INF) {
std::cout << "Infinity\n";
} else {
std::cout << dis[v][u] << "\n";
}
}
return 0;
}
T2 \({\color{green}{\text{100pts}}}\text{/100pts}\)
题意
有连接 \(n\) 个路口的 \(m\) 条双向公路,每个路口有一盏红绿灯,红绿灯各自的时长由 \(r\) 与 \(g\) 两个序列分别给出,你可以在任意时间到达一个路口,但只有当前路口为绿灯时,你才可以离开。
给出源点和汇点,求从源到汇的最短时间。
对于 \(30\%\) 的数据,\(n \le 10\),\(m \le 20\);
对于 \(100\%\) 的数据,\(n \le 2.5 \times 10^5\),\(m \le 5 \times 10^5\),\(r_i, g_i \le 10^4\),\(w \le 10^6\)。
思路
\(\text{100pts}\)
这道题就是普通 SSSP 问题的一个变体。
在松弛时,考虑当前松弛的节点处于红灯还是绿灯。如果是红灯的话,就补上一个等待时间即可。其他的就跟普通的 SSSP 一样了。
当然,你也可以拆点跑分层图,这样就不用讨论了。
这图的规模比较大,最好使用 Dijkstra,你要是能用 SPFA 卡过去也行。
代码
\(\text{100pts}\)
#include <bits/stdc++.h>
using i64 = long long;
namespace fastIO {
template<typename T>
void read(T &x) {
x = 0;
T f = 1;
char c = getchar();
while (!std::isdigit(c)) {
if (c == '-') { f = -1; }
c = getchar();
}
while (std::isdigit(c)) {
x = 10 * x + c - '0';
c = getchar();
}
x *= f;
return;
}
template<typename T, typename ...arg>
void read(T &x, arg &...args) {
read(x), read(args...);
return;
}
}
struct Graph {
struct Edge {
int u, v;
i64 w;
Edge(int _u, int _v, i64 _w) : u(_u), v(_v), w(_w) {}
};
int n;
std::vector<Edge> edges;
std::vector<int> r, g;
std::vector<std::vector<int>> e;
Graph(int _n) : n(_n + 1) {
e.resize(n);
r.resize(n), g.resize(n);
return;
}
void insert(int u, int v, int w) {
edges.emplace_back(u, v, w);
e[u].push_back(edges.size() - 1);
return;
}
void add(int u, int v, int w) {
insert(u, v, w), insert(v, u, w);
return;
}
std::vector<i64> dijkstra(int s, int t) {
std::vector<i64> dis(n, 1e18);
std::vector<bool> vis(n, false);
std::priority_queue<
std::pair<int, int>,
std::vector<std::pair<int, int>>,
std::greater<std::pair<int, int>>
> pq;
dis[s] = 0, pq.emplace(0, s);
while (!pq.empty()) {
int u = pq.top().second;
pq.pop();
if (vis[u]) {
continue;
}
vis[u] = true;
for (auto i : e[u]) {
int v = edges[i].v;
i64 w = edges[i].w, cur = dis[u] + w;
if (v != t && r[v] + g[v] && cur % (r[v] + g[v]) <= r[v]) {
cur += (r[v] - cur % (r[v] + g[v]));
}
if (dis[v] > cur) {
dis[v] = cur;
pq.emplace(dis[v], v);
}
}
}
return dis;
}
};
int main() {
int n, m, s, t;
fastIO::read(n, m, s, t);
Graph g(n);
for (int i = 1; i <= n; i++) {
fastIO::read(g.r[i], g.g[i]);
}
for (int i = 0; i < m; i++) {
int u, v;
i64 w;
fastIO::read(u, v, w);
g.add(u, v, w);
}
g.add(0, s, 0);
auto dis = g.dijkstra(0, t);
printf("%lld\n", dis[t]);
return 0;
}
T3 \({\color{orange}{\text{80pts}}}\text{/100pts}\)
题意
给出一张 DAG,每个点的出边权值相同,你可以任意改变任意一个点所有出边的权值,还需要回答以下问题:
- 该图的最长路;
- 能影响该图最长路的点的个数;
- 能影响该图最长路的所有点。
有三问,但是没有 SPJ,全部回答正确才得分。
\(n \le 10^5\)。
思路
\(\text{50pts}\)
这部分数据保证 \(n \le 1000\)。
求最长路是简单的,用拓扑跑一遍即可,我们重在解决第二三问。
这部分的数据范围在暗示你写 \(O(n^2)\) 的算法,于是你考虑枚举每个点,减小它出边的权值,然后再跑一遍拓扑来检验最长路是否有变短即可。若令 \(n\) 与 \(m\) 同级,则时间复杂度为 \(O(n^2)\)。
\(\text{100pts}\)
最长路的求法当然不变不然你给我写个更优的出来。
不难看出,第二问实际上求的是所有最长路都经过的点,证明是真的很显然,所以就不给了。
于是你把所有最长路上的店与边抠出来塞到一个新图里,当然这新图也是个 DAG,然后再跑 BFS 求出每条路径经过每个点的次数,若一个点被标记过的次数等于最长路数量,那它就是关键的。
当然也可以把它抠出来改成一个无向图跑割点,本质是一样的。
时间复杂度 \(O(n + m)\) 带超大常数。
代码
\(\text{100pts}\)
/**
* @file
* @author ForgotDream
* @brief
* @date 2023-04-28
*/
#include <bits/stdc++.h>
using i64 = long long;
using i64u = unsigned long long;
using f80 = long double;
namespace Helper {
void useFileInuput() {
#ifndef ONLINE_JUDGE
freopen("tmp.in", "r", stdin);
freopen("tmp.out", "w", stdout);
#endif
return;
}
void debug(const std::string &s) {
std::clog << s << "\n";
return;
}
}
struct Graph {
static constexpr int INF = 1e9;
struct Edge {
int u, v, w;
Edge(int _u = 0, int _v = 0, int _w = 0) : u(_u), v(_v), w(_w) {}
};
int n;
std::vector<Edge> edges;
std::vector<std::vector<int>> e;
std::vector<int> in;
Graph(int _n) : n(_n) {
e.resize(n);
in.resize(n);
return;
}
void add(int u, int v, int w) {
edges.emplace_back(u, v, w);
e[u].push_back(edges.size() - 1);
return;
}
std::vector<int> topoSort() {
std::vector<int> dis(n, -INF);
std::queue<int> q;
q.push(0), dis[0] = 0;
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto i : e[u]) {
int v = edges[i].v, w = edges[i].w;
if (!--in[v]) {
q.push(v);
}
dis[v] = std::max(dis[v], dis[u] + w);
}
}
return dis;
}
void addIn(int u) {
in[u]++;
return;
}
std::vector<int> solve() {
std::vector<int> res, dfn(n), low(n);
int clk = 0;
std::function<void(int, int)> tarjan = [&](int u, int rt) {
low[u] = dfn[u] = ++clk;
int child = 0;
for (auto i : e[u]) {
int v = edges[i].v;
if (!dfn[v]) {
tarjan(v, rt);
low[u] = std::min(low[u], low[v]);
child += (u == rt);
if (low[v] >= dfn[u] && u != rt) {
res.push_back(u);
}
} else {
low[u] = std::min(low[u], dfn[v]);
}
}
if (child >= 2 && u == rt) {
res.push_back(u);
}
return;
};
tarjan(0, 0);
return res;
}
};
signed main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
int n;
std::cin >> n;
Graph g(n + 2), rev(n + 2);
// rev is the reverse graph of the original graph.
// it's used to build another graph which only contains the special vertexes and edges
// on the longest path of the original graph.
std::vector to(n + 2, std::vector<int>());
std::vector<int> t(n + 1);
for (int i = 1; i <= n; i++) {
int c;
std::cin >> t[i] >> c;
if (c == 0) {
g.add(0, i, 0), rev.add(i, 0, 0);
g.addIn(i);
} else {
while (c--) {
int v;
std::cin >> v;
to[v].push_back(i);
g.addIn(i);
}
}
g.add(i, n + 1, t[i]), rev.add(n + 1, i, t[i]);
g.addIn(n + 1);
}
for (int i = 1; i <= n; i++) {
for (auto j : to[i]) {
g.add(i, j, t[i]), rev.add(j, i, t[i]);
}
}
auto dis = g.topoSort();
Graph rst(n + 2); // the graph mentioned above.
std::queue<int> q;
q.push(n + 1);
while (!q.empty()) {
int u = q.front();
q.pop();
for (auto i : rev.e[u]) {
int v = rev.edges[i].v, w = rev.edges[i].w;
if (dis[u] == dis[v] + w) {
rst.add(u, v, w), rst.add(v, u, w);
q.push(v);
}
}
}
auto ans = rst.solve();
std::sort(ans.begin(), ans.end());
std::cout << dis[n + 1] << "\n";
std::cout << ans.size() << "\n";
for (int i = 0; i < ans.size(); i++) {
std::cout << ans[i] << " \n"[i == ans.size() - 1];
}
return 0;
}
Epilogue
我为什么只得了 80 呢?因为我是直接记录了每个点的前驱,而没有考虑到一个点可能有多个前驱,但是运气还是比较好,混到了 80。
标签:std,27,测试题,int,edges,le,vector,dis From: https://www.cnblogs.com/forgot-dream/p/17364422.html