Initial commit: C++ courseware

This commit is contained in:
xiaolinquan committed 2026-11-05 22:51:02 +08:00
commit 4a4ee7c287
1789 files changed
+373489

No files matched your search

@@ -0,0 +1,50 @@
// P3371:非负边权的单源最短路,邻接表朴素实现。
// C++11;时间 O(n*n+m),空间 O(n+m)。
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
struct Edge {
int to;
ll w;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
if (!(cin >> n >> m >> s)) return 0;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v;
ll w;
cin >> u >> v >> w;
g[u].push_back({v, w});
}
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
vector<bool> used(n + 1, false);
dis[s] = 0; // 源点可一步不走;其他点尚未找到路线。
for (int round = 0; round < n; ++round) {
int u = -1;
for (int v = 1; v <= n; ++v) {
if (!used[v] && (u == -1 || dis[v] < dis[u])) {
u = v;
}
}
// 最小候选都不可达时,其余未确定点也不可达。
if (u == -1 || dis[u] == INF) break;
used[u] = true;
for (const Edge& e : g[u]) {
ll nd = dis[u] + e.w;
if (nd < dis[e.to]) dis[e.to] = nd;
}
}
for (int v = 1; v <= n; ++v) {
if (v > 1) cout << ' ';
cout << (dis[v] == INF ? 2147483647LL : dis[v]);
}
cout << '\n';
return 0;
}
@@ -0,0 +1,52 @@
// P3371:相同的朴素 Dijkstra,使用链式前向星保存图。
// 顶点编号为 1..n;边编号 0 表示边链结束。
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
if (!(cin >> n >> m >> s)) return 0;
vector<int> head(n + 1, 0), to(m + 5), nxt(m + 5);
vector<ll> weight(m + 5);
int cnt = 0;
auto add = [&](int u, int v, ll w) {
++cnt;
to[cnt] = v;
weight[cnt] = w;
nxt[cnt] = head[u]; // 新边先接上旧表头,再更新表头。
head[u] = cnt;
};
for (int i = 0; i < m; ++i) {
int u, v;
ll w;
cin >> u >> v >> w;
add(u, v, w);
}
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
vector<bool> used(n + 1, false);
dis[s] = 0;
for (int round = 0; round < n; ++round) {
int u = -1;
for (int v = 1; v <= n; ++v) {
if (!used[v] && (u == -1 || dis[v] < dis[u])) u = v;
}
if (u == -1 || dis[u] == INF) break;
used[u] = true;
for (int i = head[u]; i; i = nxt[i]) {
int v = to[i];
ll nd = dis[u] + weight[i];
if (nd < dis[v]) dis[v] = nd;
}
}
for (int v = 1; v <= n; ++v) {
if (v > 1) cout << ' ';
cout << (dis[v] == INF ? 2147483647LL : dis[v]);
}
cout << '\n';
return 0;
}
@@ -0,0 +1,63 @@
// 教学扩展:非负权有向图中,求 s 到 t 的距离并恢复一条最短路线。
// 输入第一行 n m s t,接着 m 行 u v w。与 P3371 的输入不同。
// 可达:第一行输出距离,第二行输出路线中的顶点编号。
// 不可达:只输出 UNREACHABLE。C++11;时间 O(n*n+m)。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
struct Edge {
int to;
ll w;
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s, t;
if (!(cin >> n >> m >> s >> t)) return 0;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v;
ll w;
cin >> u >> v >> w;
g[u].push_back({v, w});
}
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
vector<bool> used(n + 1, false);
vector<int> pre(n + 1, -1);
dis[s] = 0;
for (int round = 0; round < n; ++round) {
int u = -1;
for (int v = 1; v <= n; ++v) {
if (!used[v] && (u == -1 || dis[v] < dis[u])) u = v;
}
if (u == -1 || dis[u] == INF) break;
used[u] = true;
for (const Edge& e : g[u]) {
ll nd = dis[u] + e.w;
if (nd < dis[e.to]) {
dis[e.to] = nd;
pre[e.to] = u; // 与成功改善距离的路线保持一致。
}
}
}
// 没有路线时不能只把终点放进列表,伪装成一条路径。
if (dis[t] == INF) {
cout << "UNREACHABLE\n";
return 0;
}
vector<int> path;
for (int v = t; v != -1; v = pre[v]) path.push_back(v);
reverse(path.begin(), path.end());
cout << dis[t] << '\n';
for (size_t i = 0; i < path.size(); ++i) {
if (i) cout << ' ';
cout << path[i];
}
cout << '\n';
return 0;
}
@@ -0,0 +1,46 @@
// 教材例 10-1 / 洛谷 P3371:非负边权单源最短路。
// 输入 n m s,随后 m 行 u v w;有向边,可含重边和零权边。
#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <functional>
using namespace std;
using ll = long long;
using State = pair<ll, int>;
struct Edge { int to; ll w; };
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
if (!(cin >> n >> m >> s)) return 0;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v; ll w;
cin >> u >> v >> w;
g[u].push_back({v, w});
}
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
priority_queue<State, vector<State>, greater<State>> q;
dis[s] = 0;
q.push({0, s});
while (!q.empty()) {
State cur = q.top();
q.pop();
ll d = cur.first;
int u = cur.second;
if (d != dis[u]) continue; // 旧记录不再扫描出边
for (const auto &e : g[u]) {
int v = e.to;
ll nd = d + e.w;
if (nd < dis[v]) {
dis[v] = nd;
q.push({nd, v});
}
}
}
for (int i = 1; i <= n; ++i)
cout << (dis[i] == INF ? 2147483647LL : dis[i])
<< (i == n ? '\n' : ' ');
}
@@ -0,0 +1,34 @@
// 备份版 Bellman–Ford;输入必须没有源点可达的负环。
// 输入输出同 P3371;可以用自拟负权无环图验证,未包含判环功能。
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
struct Edge { int u, v; ll w; };
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
if (!(cin >> n >> m >> s)) return 0;
vector<Edge> edges(m);
for (auto &e : edges) cin >> e.u >> e.v >> e.w;
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
dis[s] = 0;
for (int k = 1; k < n; ++k) {
vector<ll> old = dis;
bool changed = false;
for (const auto &e : edges) {
if (old[e.u] == INF) continue;
ll nd = old[e.u] + e.w;
if (nd < dis[e.v]) {
dis[e.v] = nd;
changed = true;
}
}
if (!changed) break;
}
for (int i = 1; i <= n; ++i)
cout << (dis[i] == INF ? 2147483647LL : dis[i])
<< (i == n ? '\n' : ' ');
}
@@ -0,0 +1,46 @@
// FIFO SPFA;输入必须没有源点可达的负环,最坏时间 O(nm)。
// 输入输出同 P3371;一般负权图应使用第 4 份中的判环版本。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
using ll = long long;
struct Edge { int to; ll w; };
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, s;
if (!(cin >> n >> m >> s)) return 0;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v; ll w;
cin >> u >> v >> w;
g[u].push_back({v, w});
}
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
vector<char> in_queue(n + 1, false);
queue<int> q;
dis[s] = 0;
q.push(s);
in_queue[s] = true;
while (!q.empty()) {
int u = q.front();
q.pop();
in_queue[u] = false; // 离队后仍允许再次排队
for (const auto &e : g[u]) {
int v = e.to;
ll nd = dis[u] + e.w;
if (nd < dis[v]) {
dis[v] = nd; // 已在队列中也必须更新距离
if (!in_queue[v]) {
q.push(v);
in_queue[v] = true;
}
}
}
}
for (int i = 1; i <= n; ++i)
cout << (dis[i] == INF ? 2147483647LL : dis[i])
<< (i == n ? '\n' : ' ');
}
@@ -0,0 +1,50 @@
// P4568 / 教材例 10-2:隐式分层图,最多 k 次免费。
// n m k;s t;m 行 u v w。城市从 0 编号,无向非负边。
// 课堂扩展约定:如果没有路线,输出 -1;原题未指定此情况的输出。
#include <iostream>
#include <vector>
#include <queue>
#include <tuple>
#include <functional>
#include <algorithm>
using namespace std;
using ll = long long;
using State = tuple<ll, int, int>;
struct Edge { int to; ll w; };
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m, k, s, t;
if (!(cin >> n >> m >> k >> s >> t)) return 0;
vector<vector<Edge>> g(n);
for (int i = 0; i < m; ++i) {
int u, v; ll w; cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
const ll INF = (1LL << 60);
vector<vector<ll>> dis(k + 1, vector<ll>(n, INF));
priority_queue<State, vector<State>, greater<State>> q;
dis[0][s] = 0;
q.push({0, s, 0});
while (!q.empty()) {
State cur = q.top(); q.pop();
ll d = get<0>(cur);
int u = get<1>(cur), j = get<2>(cur);
if (d != dis[j][u]) continue;
for (const auto &e : g[u]) {
int v = e.to;
ll nd = d + e.w;
if (nd < dis[j][v]) {
dis[j][v] = nd;
q.push({nd, v, j});
}
if (j < k && d < dis[j + 1][v]) {
dis[j + 1][v] = d;
q.push({d, v, j + 1});
}
}
}
ll ans = INF;
for (int j = 0; j <= k; ++j) ans = min(ans, dis[j][t]);
cout << (ans == INF ? -1 : ans) << '\n';
}
@@ -0,0 +1,46 @@
// P1629 / 教材例 10-3:有向正权图,题目保证任意两点互相可达。
// 输入 n m,随后 m 行 u v w;顶点从 1 编号。
#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <functional>
using namespace std;
using ll = long long;
using State = pair<ll, int>;
struct Edge { int to; ll w; };
vector<ll> dijkstra(const vector<vector<Edge>> &g, int s) {
const ll INF = (1LL << 60);
vector<ll> dis(g.size(), INF);
priority_queue<State, vector<State>, greater<State>> q;
dis[s] = 0; q.push({0, s});
while (!q.empty()) {
State cur = q.top(); q.pop();
ll d = cur.first; int u = cur.second;
if (d != dis[u]) continue;
for (const auto &e : g[u]) {
ll nd = d + e.w;
if (nd < dis[e.to]) {
dis[e.to] = nd;
q.push({nd, e.to});
}
}
}
return dis;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m;
if (!(cin >> n >> m)) return 0;
vector<vector<Edge>> g(n + 1), rg(n + 1);
for (int i = 0; i < m; ++i) {
int u, v; ll w; cin >> u >> v >> w;
g[u].push_back({v, w});
rg[v].push_back({u, w});
}
vector<ll> out = dijkstra(g, 1);
vector<ll> back = dijkstra(rg, 1);
ll ans = 0;
for (int i = 2; i <= n; ++i) ans += out[i] + back[i];
cout << ans << '\n';
}
@@ -0,0 +1,46 @@
// 教学扩展:非负有向图,多源最短路。
// 输入 n m r;下一行 r 个源点编号;随后 m 行 u v w。
// 顶点编号 1..n,允许重复源点;输出到各点的最近距离,不可达输出 -1。
#include <iostream>
#include <vector>
#include <queue>
#include <utility>
#include <functional>
using namespace std;
using ll = long long;
using State = pair<ll, int>;
struct Edge { int to; ll w; };
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m, r;
if (!(cin >> n >> m >> r)) return 0;
vector<int> sources(r);
for (int &x : sources) cin >> x;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v; ll w; cin >> u >> v >> w;
g[u].push_back({v, w});
}
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
priority_queue<State, vector<State>, greater<State>> q;
for (int x : sources) {
if (dis[x] == 0) continue;
dis[x] = 0;
q.push({0, x});
}
while (!q.empty()) {
State cur = q.top(); q.pop();
ll d = cur.first; int u = cur.second;
if (d != dis[u]) continue;
for (const auto &e : g[u]) {
ll nd = d + e.w;
if (nd < dis[e.to]) {
dis[e.to] = nd;
q.push({nd, e.to});
}
}
}
for (int i = 1; i <= n; ++i)
cout << (dis[i] == INF ? -1 : dis[i]) << (i == n ? '\n' : ' ');
}
@@ -0,0 +1,53 @@
// P3385:检测从 1 可达的负环。C++11。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
using ll = long long;
struct Edge { int v, w; };
bool hasNegativeCycle(const vector<vector<Edge>>& g) {
int n = static_cast<int>(g.size()) - 1;
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
vector<int> len(n + 1, 0);
vector<bool> inq(n + 1, false);
queue<int> q;
dis[1] = 0;
q.push(1); inq[1] = true;
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = false;
for (auto e : g[u]) {
int v = e.v;
ll nd = dis[u] + e.w;
if (nd >= dis[v]) continue;
dis[v] = nd;
len[v] = len[u] + 1;
if (len[v] >= n) return true;
if (!inq[v]) {
q.push(v);
inq[v] = true;
}
}
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
if (!(cin >> T)) return 0;
while (T--) {
int n, m; cin >> n >> m;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int u, v, w; cin >> u >> v >> w;
g[u].push_back({v, w});
if (w >= 0) g[v].push_back({u, w});
}
bool bad = hasNegativeCycle(g);
cout << (bad ? "YES" : "NO") << '\n';
}
}
@@ -0,0 +1,44 @@
// 教学版 Bellman-Ford 判环;输入格式与 P3385 一致。C++11。
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
struct Edge { int u, v, w; };
bool hasNegativeCycle(int n, const vector<Edge>& edges, int s) {
const ll INF = (1LL << 60);
vector<ll> dis(n + 1, INF);
dis[s] = 0;
for (int k = 1; k <= n; ++k) {
auto old = dis;
bool changed = false;
for (auto e : edges) {
if (old[e.u] == INF) continue;
ll nd = old[e.u] + e.w;
if (nd < dis[e.v]) {
dis[e.v] = nd;
changed = true;
}
}
if (!changed) return false;
if (k == n) return true;
}
return false;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
if (!(cin >> T)) return 0;
while (T--) {
int n, m; cin >> n >> m;
vector<Edge> edges;
for (int i = 0; i < m; ++i) {
int u, v, w; cin >> u >> v >> w;
edges.push_back({u, v, w});
if (w >= 0) edges.push_back({v, u, w});
}
cout << (hasNegativeCycle(n, edges, 1) ? "YES" : "NO") << '\n';
}
}
@@ -0,0 +1,50 @@
// P5960:求任意一组差分约束可行解。C++11。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
using ll = long long;
struct Edge { int v, w; };
bool solve(const vector<vector<Edge>>& g, vector<ll>& dis) {
int n = static_cast<int>(g.size()) - 1;
dis.assign(n + 1, 0);
vector<int> len(n + 1, 0);
vector<bool> inq(n + 1, true);
queue<int> q;
for (int i = 1; i <= n; ++i) q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop();
inq[u] = false;
for (auto e : g[u]) {
int v = e.v;
ll nd = dis[u] + e.w;
if (nd >= dis[v]) continue;
dis[v] = nd;
len[v] = len[u] + 1;
if (len[v] >= n) return false;
if (!inq[v]) {
q.push(v); inq[v] = true;
}
}
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
if (!(cin >> n >> m)) return 0;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < m; ++i) {
int a, b, w; cin >> a >> b >> w;
g[b].push_back({a, w});
}
vector<ll> dis;
if (!solve(g, dis)) cout << "NO\n";
else {
for (int i = 1; i <= n; ++i)
cout << dis[i] << (i == n ? '\n' : ' ');
}
}
@@ -0,0 +1,56 @@
// P2865:正权无向图,允许重复点和边的严格次短路。C++11。
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include <utility>
#include <algorithm>
using namespace std;
using ll = long long;
struct Edge { int v, w; };
ll secondShortest(const vector<vector<Edge>>& g) {
int n = static_cast<int>(g.size()) - 1;
const ll INF = (1LL << 60);
vector<ll> d1(n + 1, INF);
vector<ll> d2(n + 1, INF);
using State = pair<ll, int>;
priority_queue<State, vector<State>, greater<State>> q;
d1[1] = 0;
q.push({0, 1});
while (!q.empty()) {
State cur = q.top(); q.pop();
ll d = cur.first;
int u = cur.second;
if (d != d1[u] && d != d2[u]) continue;
for (auto e : g[u]) {
int v = e.v;
ll nd = d + e.w;
if (nd < d1[v]) {
// 新最短留下,旧最短继续竞争第二标签。
swap(nd, d1[v]);
q.push({d1[v], v});
}
if (d1[v] < nd && nd < d2[v]) {
d2[v] = nd;
q.push({d2[v], v});
}
}
}
return d2[n];
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, r;
if (!(cin >> n >> r)) return 0;
vector<vector<Edge>> g(n + 1);
for (int i = 0; i < r; ++i) {
int u, v, w; cin >> u >> v >> w;
g[u].push_back({v, w});
g[v].push_back({u, w});
}
// 原题保证严格次短存在。
cout << secondShortest(g) << '\n';
}
@@ -0,0 +1,29 @@
// P2910:完整非负矩阵,按规定顺序访问的最小总危险值。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
void floyd(vector<vector<ll>>& d, int n) {
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<int> a(m);
for (int t = 0; t < m; ++t) cin >> a[t];
vector<vector<ll>> d(n + 1, vector<ll>(n + 1));
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) cin >> d[i][j];
floyd(d, n);
ll ans = 0;
for (int t = 1; t < m; ++t) ans += d[a[t - 1]][a[t]];
cout << ans << '\n';
}
@@ -0,0 +1,39 @@
// 教学模板:有向图,无负环。输入 n m,随后 m 行 u v w。
// 输出 n 行最短距离矩阵,不可达写 INF。这个输出约定为教学自拟。
// 建议教学数据 n<=500、|w|<=10^9,有限路长和候选之和远小于 INF。
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
using ll = long long;
const ll INF = 1LL << 60;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<vector<ll>> d(n + 1, vector<ll>(n + 1, INF));
for (int i = 1; i <= n; ++i) d[i][i] = 0;
for (int t = 0; t < m; ++t) {
int u, v;
ll w;
cin >> u >> v >> w;
d[u][v] = min(d[u][v], w);
}
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
if (d[i][k] == INF || d[k][j] == INF) continue;
ll nd = d[i][k] + d[k][j];
d[i][j] = min(d[i][j], nd);
}
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= n; ++j) {
if (j > 1) cout << ' ';
if (d[i][j] == INF) cout << "INF";
else cout << d[i][j];
}
cout << '\n';
}
}
@@ -0,0 +1,27 @@
// B3611:至少经过一条边的可达性,不预先把对角线设为 true。
#include <iostream>
using namespace std;
bool reach[105][105]{};
void closure(int n) {
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
reach[i][j] = reach[i][j] || (reach[i][k] && reach[k][j]);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, x;
cin >> n;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j) {
cin >> x;
reach[i][j] = (x != 0);
}
closure(n);
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
cout << int(reach[i][j]) << (j == n ? '\n' : ' ');
}
@@ -0,0 +1,29 @@
// P1144 最短路计数。C++17;无向无权图,重边按不同边计数。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
vector<int> head(n + 1, -1), to(2 * m), nxt(2 * m);
int tot = 0;
auto add = [&](int u, int v) {
to[tot] = v; nxt[tot] = head[u]; head[u] = tot++;
};
for (int i = 0, u, v; i < m; ++i) {
cin >> u >> v; add(u, v); add(v, u);
}
vector<int> dist(n + 1, -1), ways(n + 1, 0);
queue<int> q; dist[1] = 0; ways[1] = 1; q.push(1);
while (!q.empty()) {
int u = q.front(); q.pop();
for (int e = head[u]; e != -1; e = nxt[e]) {
int v = to[e];
if (dist[v] == -1) {
dist[v] = dist[u] + 1; q.push(v);
}
if (dist[v] == dist[u] + 1)
ways[v] = (ways[v] + ways[u]) % 100003;
}
}
for (int i = 1; i <= n; ++i) cout << ways[i] << '\n';
}
@@ -0,0 +1,44 @@
// P1875 魔法药水。每个原料位置独立选择制法,交换配方书写顺序不重复计数。
// 当前原题提示同一对原料可能有不同产物,必须保存所有不同配方。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll addSat(ll a, ll b) { return a > LLONG_MAX - b ? LLONG_MAX : a + b; }
ll mulSat(ll a, ll b) {
if (a == 0 || b == 0) return 0;
return a > LLONG_MAX / b ? LLONG_MAX : a * b;
}
struct Recipe { int a, b, c; };
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n; if (!(cin >> n)) return 0;
vector<ll> d(n), ways(n, 1);
for (ll &x : d) cin >> x;
vector<Recipe> rec;
vector<vector<int>> incident(n);
unordered_set<ll> seen;
int a, b, c;
while (cin >> a >> b >> c) {
if (a > b) swap(a, b);
ll key = (1LL * a * n + b) * n + c;
if (!seen.insert(key).second) continue;
int id = (int)rec.size(); rec.push_back({a, b, c});
incident[a].push_back(id); incident[b].push_back(id);
}
vector<int> need(rec.size(), 2);
vector<bool> done(n, false);
for (int round = 0; round < n; ++round) {
int u = -1;
for (int i = 0; i < n; ++i)
if (!done[i] && (u == -1 || d[i] < d[u])) u = i;
done[u] = true;
for (int id : incident[u]) {
if (--need[id] != 0) continue;
auto [a, b, c] = rec[id];
ll nd = d[a] + d[b], cnt = mulSat(ways[a], ways[b]);
if (nd < d[c]) { d[c] = nd; ways[c] = cnt; }
else if (nd == d[c]) ways[c] = addSat(ways[c], cnt);
}
}
cout << d[0] << ' ' << ways[0] << '\n';
}
@@ -0,0 +1,42 @@
// P1462 通往奥格瑞玛的道路。城市费二分,非负道路耗血用 Dijkstra。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; ll b; if (!(cin >> n >> m >> b)) return 0;
vector<int> fee(n + 1), values;
for (int i = 1; i <= n; ++i) { cin >> fee[i]; values.push_back(fee[i]); }
vector<vector<pair<int,ll>>> g(n + 1);
for (int i = 0, u, v; i < m; ++i) {
ll w; cin >> u >> v >> w;
g[u].push_back({v,w}); g[v].push_back({u,w});
}
auto check = [&](int X) {
if (fee[1] > X || fee[n] > X) return false;
const ll INF = 1LL << 60;
vector<ll> dist(n + 1, INF);
priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<pair<ll,int>>> pq;
dist[1] = 0; pq.push({0,1});
while (!pq.empty()) {
auto [du,u] = pq.top(); pq.pop();
if (du != dist[u]) continue;
for (auto [v,w] : g[u]) {
if (fee[v] > X) continue;
if (du + w < dist[v]) {
dist[v] = du + w; pq.push({dist[v],v});
}
}
}
return dist[n] <= b;
};
sort(values.begin(), values.end());
values.erase(unique(values.begin(), values.end()), values.end());
if (!check(values.back())) { cout << "AFK\n"; return 0; }
int l = 0, r = (int)values.size() - 1;
while (l < r) {
int mid = l + (r-l)/2;
if (check(values[mid])) r = mid; else l = mid + 1;
}
cout << values[l] << '\n';
}
@@ -0,0 +1,42 @@
// P1073 最优贸易。价格是 1..100,单调标签至多改善 100 次。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
vector<int> price(n + 1);
for (int i = 1; i <= n; ++i) cin >> price[i];
vector<vector<int>> g(n + 1), rg(n + 1);
auto add = [&](int u, int v) { g[u].push_back(v); rg[v].push_back(u); };
for (int i = 0, u, v, z; i < m; ++i) {
cin >> u >> v >> z; add(u,v); if (z == 2) add(v,u);
}
vector<int> low(n + 1, 101), high(n + 1, -1);
vector<bool> inq(n + 1, false);
queue<int> q; low[1] = price[1]; q.push(1); inq[1] = true;
while (!q.empty()) {
int u = q.front(); q.pop(); inq[u] = false;
for (int v : g[u]) {
int cand = min(low[u], price[v]);
if (cand < low[v]) {
low[v] = cand;
if (!inq[v]) { q.push(v); inq[v] = true; }
}
}
}
high[n] = price[n]; q.push(n); inq[n] = true;
while (!q.empty()) {
int u = q.front(); q.pop(); inq[u] = false;
for (int v : rg[u]) {
int cand = max(high[u], price[v]);
if (cand > high[v]) {
high[v] = cand;
if (!inq[v]) { q.push(v); inq[v] = true; }
}
}
}
int ans = 0;
for (int v = 1; v <= n; ++v)
if (low[v] <= 100 && high[v] >= 1) ans = max(ans, high[v]-low[v]);
cout << ans << '\n';
}
@@ -0,0 +1,29 @@
// P1993 小K的农场。上界边 x[v] <= x[u]+w,全图判负环。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
vector<vector<pair<int,ll>>> g(n + 1);
auto add = [&](int u, int v, ll w) { g[u].push_back({v,w}); };
for (int i = 0, type, a, b; i < m; ++i) {
cin >> type >> a >> b; ll c;
if (type == 1) { cin >> c; add(a,b,-c); }
else if (type == 2) { cin >> c; add(b,a,c); }
else { add(a,b,0); add(b,a,0); }
}
vector<ll> dist(n + 1, 0);
vector<int> len(n + 1, 0);
vector<bool> inq(n + 1, true);
queue<int> q; for (int i = 1; i <= n; ++i) q.push(i);
while (!q.empty()) {
int u = q.front(); q.pop(); inq[u] = false;
for (auto [v,w] : g[u]) if (dist[v] > dist[u] + w) {
dist[v] = dist[u] + w; len[v] = len[u] + 1;
if (len[v] >= n) { cout << "No\n"; return 0; }
if (!inq[v]) { q.push(v); inq[v] = true; }
}
}
cout << "Yes\n";
}
@@ -0,0 +1,68 @@
// P3275 糖果。迭代 Kosaraju + 缩点 DAG 上的最长下界,O(n+k)。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, k; if (!(cin >> n >> k)) return 0;
vector<vector<pair<int,int>>> g(n);
vector<vector<int>> rg(n);
auto add = [&](int u, int v, int w) { g[u].push_back({v,w}); rg[v].push_back(u); };
for (int i = 0, x, a, b; i < k; ++i) {
cin >> x >> a >> b; --a; --b;
if (x == 1) { add(a,b,0); add(b,a,0); }
if (x == 2) add(a,b,1);
if (x == 3) add(b,a,0);
if (x == 4) add(b,a,1);
if (x == 5) add(a,b,0);
}
vector<bool> vis(n, false);
vector<int> order;
// 第一次 DFS:显式保存下一条边下标,退出时记录结束序。
for (int s = 0; s < n; ++s) if (!vis[s]) {
vector<pair<int,int>> st;
vis[s] = true; st.push_back({s,0});
while (!st.empty()) {
auto &[u,i] = st.back();
if (i == (int)g[u].size()) { order.push_back(u); st.pop_back(); }
else {
int v = g[u][i++].first;
if (!vis[v]) { vis[v] = true; st.push_back({v,0}); }
}
}
}
// 第二次 DFS:反图按逆结束序分组。
vector<int> comp(n, -1), size;
int count = 0;
reverse(order.begin(), order.end());
for (int s : order) if (comp[s] == -1) {
vector<int> st(1,s); comp[s] = count; int amount = 0;
while (!st.empty()) {
int u = st.back(); st.pop_back(); ++amount;
for (int v : rg[u]) if (comp[v] == -1) {
comp[v] = count; st.push_back(v);
}
}
size.push_back(amount); ++count;
}
vector<vector<pair<int,int>>> dag(count);
vector<int> indeg(count, 0), d(count, 1);
for (int u = 0; u < n; ++u) for (auto [v,w] : g[u]) {
if (comp[u] == comp[v]) {
if (w == 1) { cout << -1 << '\n'; return 0; }
} else {
dag[comp[u]].push_back({comp[v],w}); ++indeg[comp[v]];
}
}
queue<int> q;
for (int c = 0; c < count; ++c) if (indeg[c] == 0) q.push(c);
while (!q.empty()) {
int u = q.front(); q.pop();
for (auto [v,w] : dag[u]) {
d[v] = max(d[v], d[u]+w);
if (--indeg[v] == 0) q.push(v);
}
}
long long ans = 0;
for (int c = 0; c < count; ++c) ans += 1LL * size[c] * d[c];
cout << ans << '\n';
}
@@ -0,0 +1,29 @@
// P6464 传送门。原图 Floyd 一次,独立枚举一对零边。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
const ll INF = 1LL << 60;
vector<vector<ll>> d(n, vector<ll>(n, INF));
for (int i = 0; i < n; ++i) d[i][i] = 0;
for (int e = 0, u, v; e < m; ++e) {
ll w; cin >> u >> v >> w; --u; --v;
d[u][v] = d[v][u] = min(d[u][v], w);
}
for (int k = 0; k < n; ++k)
for (int i = 0; i < n; ++i)
for (int j = 0; j < n; ++j)
if (d[i][k] != INF && d[k][j] != INF)
d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
ll ans = 0;
for (int i = 0; i < n; ++i) for (int j = i+1; j < n; ++j) ans += d[i][j];
for (int u = 0; u < n; ++u) for (int v = u+1; v < n; ++v) {
ll sum = 0;
for (int i = 0; i < n; ++i) for (int j = i+1; j < n; ++j)
sum += min({d[i][j], d[i][u]+d[v][j], d[i][v]+d[u][j]});
ans = min(ans, sum);
}
cout << ans << '\n';
}
@@ -0,0 +1,28 @@
// P3403 跳楼机。楼层减一,按最小步长的余数建图。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
ll h; array<int,3> step;
if (!(cin >> h >> step[0] >> step[1] >> step[2])) return 0;
sort(step.begin(), step.end()); int m = step[0];
const ll INF = 1LL << 60;
vector<ll> d(m, INF);
priority_queue<pair<ll,int>, vector<pair<ll,int>>, greater<pair<ll,int>>> pq;
d[0] = 0; pq.push({0,0});
while (!pq.empty()) {
auto [du,u] = pq.top(); pq.pop();
if (du != d[u]) continue;
for (int i = 1; i <= 2; ++i) {
int w = step[i], v = (u+w)%m;
if (du + w < d[v]) { d[v] = du+w; pq.push({d[v],v}); }
}
}
ll L = h-1, ans = 0;
for (int r = 0; r < m; ++r) {
if (d[r] == INF || d[r] > L) continue;
ans += (L-d[r])/m + 1;
}
cout << ans << '\n';
}
@@ -0,0 +1,28 @@
// P1119 灾后重建。原题编号 0..n-1,重建时间和询问时间分别不下降。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
vector<int> repair(n); for (int &t : repair) cin >> t;
const ll INF = 1LL << 60;
vector<vector<ll>> d(n, vector<ll>(n, INF));
for (int i = 0; i < n; ++i) d[i][i] = 0;
for (int e = 0, u, v; e < m; ++e) {
ll w; cin >> u >> v >> w; d[u][v] = d[v][u] = min(d[u][v],w);
}
int Q, k = 0; cin >> Q;
while (Q--) {
int x, y, t; cin >> x >> y >> t;
while (k < n && repair[k] <= t) {
for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) {
if (d[i][k] == INF || d[k][j] == INF) continue;
d[i][j] = min(d[i][j], d[i][k]+d[k][j]);
}
++k;
}
if (repair[x] > t || repair[y] > t || d[x][y] == INF) cout << -1 << '\n';
else cout << d[x][y] << '\n';
}
}
@@ -0,0 +1,28 @@
// P1037 产生数。十个数字的自反传递闭包 + 十进制高精度乘法。
#include <bits/stdc++.h>
using namespace std;
string mulSmall(string s, int k) {
int carry = 0;
for (int i = (int)s.size()-1; i >= 0; --i) {
int t = (s[i]-'0')*k + carry;
s[i] = char('0'+t%10); carry = t/10;
}
while (carry) { s.insert(s.begin(), char('0'+carry%10)); carry /= 10; }
return s;
}
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
string number; int k; if (!(cin >> number >> k)) return 0;
bool reach[10][10] = {};
for (int i = 0; i < 10; ++i) reach[i][i] = true;
for (int i = 0, a, b; i < k; ++i) { cin >> a >> b; reach[a][b] = true; }
for (int mid = 0; mid < 10; ++mid)
for (int i = 0; i < 10; ++i)
for (int j = 0; j < 10; ++j)
reach[i][j] |= reach[i][mid] && reach[mid][j];
int choices[10] = {};
for (int a = 0; a < 10; ++a) for (int b = 0; b < 10; ++b) choices[a] += reach[a][b];
string ans = "1";
for (char digit : number) ans = mulSmall(ans, choices[digit-'0']);
cout << ans << '\n';
}
@@ -0,0 +1,21 @@
// P2419 牛大赛。胜者指向败者,闭包后判断是否与其余点全部可比较。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
vector<vector<bool>> reach(n, vector<bool>(n, false));
for (int i = 0, a, b; i < m; ++i) { cin >> a >> b; reach[a-1][b-1] = true; }
for (int k = 0; k < n; ++k) for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j)
reach[i][j] = reach[i][j] || (reach[i][k] && reach[k][j]);
int ans = 0;
for (int v = 0; v < n; ++v) {
int known = 0;
for (int j = 0; j < n; ++j) {
if (j == v) continue;
if (reach[v][j] || reach[j][v]) ++known;
}
if (known == n-1) ++ans;
}
cout << ans << '\n';
}
@@ -0,0 +1,34 @@
// P2047 社交网络。正权、无向、简单、连通图;有序点对,排除端点。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
ios::sync_with_stdio(false); cin.tie(nullptr);
int n, m; if (!(cin >> n >> m)) return 0;
const ll INF = 1LL << 60;
vector<vector<ll>> d(n, vector<ll>(n, INF));
// 中间受限图的计数可能超过最终答案范围,从存储起即用浮点。
vector<vector<long double>> cnt(n, vector<long double>(n, 0));
for (int i = 0; i < n; ++i) d[i][i] = 0;
for (int e = 0, u, v; e < m; ++e) {
ll w; cin >> u >> v >> w; --u; --v;
d[u][v] = d[v][u] = w; cnt[u][v] = cnt[v][u] = 1;
}
for (int k = 0; k < n; ++k) for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) {
if (i == k || j == k || i == j) continue;
if (d[i][k] == INF || d[k][j] == INF) continue;
ll nd = d[i][k]+d[k][j];
long double ways = cnt[i][k]*cnt[k][j];
if (nd < d[i][j]) { d[i][j] = nd; cnt[i][j] = ways; }
else if (nd == d[i][j]) cnt[i][j] += ways;
}
cout << fixed << setprecision(3);
for (int v = 0; v < n; ++v) {
long double ans = 0;
for (int s = 0; s < n; ++s) for (int t = 0; t < n; ++t) {
if (s == t || s == v || t == v) continue;
if (d[s][v]+d[v][t] == d[s][t]) ans += cnt[s][v]*cnt[v][t]/cnt[s][t];
}
cout << ans << '\n';
}
}
@@ -0,0 +1,51 @@
# 源码说明
共 28 份独立完整程序。建议全部按 GCC C++17 编译。每个文件含独立 `main`,应单独编译。输入来自标准输入,结果写到标准输出。
## 前六份课件
| 文件 | 对应内容 | 输入与输出约定 |
| --- | --- | --- |
| 01_dijkstra_naive.cpp | P3371,邻接表朴素 Dijkstra | n m s;m 条有向边;输出源点到各点距离 |
| 01b_dijkstra_forward_star.cpp | P3371,前向星朴素 Dijkstra | 同上;改变存图方式 |
| 01c_dijkstra_path.cpp | 第一章路径恢复扩展 | 教学输入格式与路径输出见源码注释及第一章备注 |
| 02_dijkstra_heap.cpp | P3371,堆优化 Dijkstra | 同上;非负边权 |
| 02b_bellman_ford.cpp | 备份版 Bellman–Ford | 同 P3371 输入输出;教学扩展允许负边,无源点可达负环 |
| 02c_spfa.cpp | FIFO SPFA | 同上;此版不带判环,最坏 O(nm) |
| 03_flights.cpp | P4568,分层状态 | n m k;s t;m 条无向边;0 开始编号;不可达输出 -1 是教学扩展约定 |
| 03b_postman.cpp | P1629,反图与往返 | n m;m 条有向边;1 开始编号;按原题保证各点互达 |
| 03c_multi_source.cpp | 多源最短路教学扩展 | n m r;r 个源点;m 条有向边;不可达输出 -1 |
| 04_negative_cycle.cpp | P3385,SPFA 判环 | 多组数据;仅检测从 1 可达的负环;非负输入边双向、负边单向 |
| 04a_bf_negative.cpp | P3385,Bellman–Ford 判环 | 同上一文件 |
| 04b_difference_constraints.cpp | P5960,差分约束 | a b c 表示 x[a]−x[b]≤c;输出任意解,无解为 NO |
| 05_second_shortest.cpp | P2865,严格次短路 | 正权无向图;允许重复经过点边,第二距离严格大于最短距离 |
| 06_floyd_trip.cpp | P2910,规定访问顺序 | 读入访问序列和完整危险值矩阵,输出最小总危险值 |
| 06a_floyd_general.cpp | 通用 Floyd 教学模板 | n m;m 条有向边;无负环;输出矩阵,不可达为 INF |
| 06b_transitive_closure.cpp | B3611,非空路径闭包 | 输入邻接矩阵;不预先把对角线设为真 |
P3371 系列程序以 2147483647 表示不可达。通用 Floyd 教学模板建议 n≤500、边权绝对值≤10^9,确保所选 INF 和 `long long` 有足够余量。
## 综合习题的重要前提
`07_01_p1144.cpp` 至 `07_12_p2047.cpp` 与课程使用说明的 12 道习题一一对应,输入输出遵循相应题号。
- P1144:重边按不同边计数,模 100003。
- P1875:配方读到 EOF;保存同一原料对的不同产物,完全重复的无序配方去重。主算法 O(n²+R),哈希去重的 O(R) 是期望复杂度。无关药水极大中间计数采用饱和运算,原题承诺范围内的目标答案保持精确。
- P1462:费用包括起终点,耗血恰好等于初始血量仍可行。
- P1073:传播次数的界依赖价格范围 1…100。
- P1993:全图判定约束可行性,所有点一起初始化。
- P3275:非递归强连通分量与缩点,避免深链递归栈溢出;总量用 `long long`。
- P6464:每次试装传送门都读取原图 Floyd 距离。
- P3403:从第 1 层开始计数,先排除不可达再计数,覆盖 h=2^63−1 边界。
- P1119:依赖原题重建时间、询问时间分别不降的保证;0 开始编号。
- P1037:允许零次变换,对角线置真;按最终数字去重,使用十进制高精度字符串。
- P2419:原题胜负关系无矛盾,检查是否与其余所有点可比较。
- P2047:正权简单无向图;有序点对双向累计并排除端点,使用 `long double` 计数与求比例,输出三位小数。
## 验证
本次以 C++17 重新编译第 2—7 章的 25 份程序,31 组新增教学输入及关键边界检查全部通过。输入输出见 `../教学样例/样例清单.json`,对应记录见 `../资料与核查/修订样例验证.json`。
第一章 3 份程序的修订验证记录另见 `../资料与核查/第一章程序核查.json`。旧版 27 份程序累计 3,972 组本地对照记录保留在 `../资料与核查/旧版程序验证`,本次没有重复执行全部历史测试。
所有验证均为本地编译运行,没有向在线评测提交。