选择若干乡镇作为额外节点,用最低费用让原城市连通。
P14362 [CSP-S 2025] 道路修复
枚举乡镇子集 + Kruskal:把大图先缩成原图 MST,再加入乡镇连边。
一、先回答问题
直接对每个子集跑 m 条原边的 Kruskal,状态数乘上 m 会过大。
二、核心观察
建模固定乡镇集合后,在原城市和这些乡镇组成的图上求 MST。
优化原有道路先求 MST,只保留 n-1 条边;每个子集不再扫描 10^6 条原道路。
正确性加入额外边后,原图中真正需要参与竞争的原边可以由原图 MST 提供;非 MST 原边不会成为必要选择。
三、互动演示
--
--
--
1:准备开始。
四、自己选答案
为什么要枚举乡镇子集?
先自己判断,再点选项。
费用必须用什么类型?
先自己判断,再点选项。
五、手推结果
| 阶段 | 边集合 | 作用 |
|---|---|---|
| 预处理 | 原图全部 m 条边 | 求原城市 MST,保留 n-1 条 |
| 枚举 mask | 被选乡镇到城市的边 | 决定哪些乡镇可用 |
| Kruskal | MST 边 + mask 乡镇边 | 连接原城市和被选乡镇 |
| 取最小 | 所有 mask | 得到全局最优 |
六、C++14 参考代码
可提交代码
#include <bits/stdc++.h>
using namespace std;
struct DSU {
vector<int> fa, sz;
DSU(int n = 0) { init(n); }
void init(int n) {
fa.resize(n + 1);
sz.assign(n + 1, 1);
iota(fa.begin(), fa.end(), 0);
}
int find(int x) {
while (fa[x] != x) {
fa[x] = fa[fa[x]];
x = fa[x];
}
return x;
}
bool unite(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return false;
if (sz[x] < sz[y]) swap(x, y);
fa[y] = x;
sz[x] += sz[y];
return true;
}
};
struct Edge {
int u, v;
long long w;
int town; // -1 表示原城市道路,否则表示属于哪个乡镇
bool operator<(const Edge &other) const {
return w < other.w;
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m, k;
cin >> n >> m >> k;
vector<Edge> original;
original.reserve(m);
for (int i = 0; i < m; i++) {
int u, v;
long long w;
cin >> u >> v >> w;
original.push_back({u, v, w, -1});
}
vector<long long> c(k);
vector<vector<long long>> a(k, vector<long long>(n + 1));
for (int j = 0; j < k; j++) {
cin >> c[j];
for (int i = 1; i <= n; i++) cin >> a[j][i];
}
sort(original.begin(), original.end());
DSU dsu(n + k);
vector<Edge> useful;
useful.reserve(max(0, n - 1) + k * n);
// 原图中只保留 MST 边。
for (const auto &e : original) {
if (dsu.unite(e.u, e.v)) useful.push_back(e);
}
for (int j = 0; j < k; j++) {
int townId = n + j + 1;
for (int i = 1; i <= n; i++) {
useful.push_back({townId, i, a[j][i], j});
}
}
sort(useful.begin(), useful.end());
int totalMask = 1 << k;
vector<long long> townCost(totalMask, 0);
for (int mask = 1; mask < totalMask; mask++) {
int bit = __builtin_ctz(mask);
townCost[mask] = townCost[mask ^ (1 << bit)] + c[bit];
}
const long long INF = (1LL << 62);
long long ans = INF;
for (int mask = 0; mask < totalMask; mask++) {
dsu.init(n + k);
long long cur = townCost[mask];
int need = n + __builtin_popcount((unsigned)mask) - 1;
int got = 0;
if (need == 0) {
ans = min(ans, cur);
continue;
}
for (const auto &e : useful) {
if (e.town != -1 && ((mask >> e.town) & 1) == 0) continue;
if (dsu.unite(e.u, e.v)) {
cur += e.w;
got++;
if (got == need) break;
}
}
if (got == need) ans = min(ans, cur);
}
cout << ans << '\n';
return 0;
}
`town = -1` 表示原城市 MST 边。
每个 `mask` 都重新初始化并查集。
`need = n + popcount(mask) - 1` 是当前生成树需要的边数。