题意
给定一个 \(n\) 个点 \(m\) 条边的无向图,求出无向图的一棵最小生成树,满足给定节点的度数不超过 \(k\) 。
算法
设给定的节点为 \(v_0\) 。
- 先求出最小生成树,将与 \(v_0\) 相连的边删去,这样就会产生一些联通分量,设有 \(m\) 个联通分量。
- 我们需要再将 \(v_0\) 与这 \(m\) 个联通分量连 \(k\) 条边,使 \(v_0\) 的度数为 \(k\) 且保证图连通。
- 所以当 \(m>k\) 时,问题无解;当 \(m\le k\) 时,我们需要找到 \(k-m\) 条边与 \(v_0\) 相连且需使图为一棵最小生成树。
关于如何找到这 \(k-m\) 条边,可以用动态规划求出。
设 dp[v]
为 \(v\) 到 \(v_0\) 上权值最大且与 \(v_0\) 不相连的边,则有
\[dp[v]=max(dp[father[v]],w[father[v] \rightarrow v])\]
因为只考虑权值最大的边,我们设 \(v'\) 与 \(v_0\) 相连,将 \(dp[v_0].w\) 与 \(dp[v'].w\) 赋值为 \(-INF\) 。
找 \(k-m\) 次这样可以替换与 \(v_0\) 相邻的边,把它与 \(v_0\) 相连并把被替换的边删去即可求出答案。
例题
我们将字符串用 \(\texttt{map}\) 储存,并将 \(\texttt{mp["Park"]}\) 赋值为 \(1\) 就可以套模板了。
还有一道一样的题:Luogu
#include <map>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 103
#define il inline
#define re register
#define INF 0x3f3f3f3f
#define tie0 cin.tie(0),cout.tie(0)
#define fastio ios::sync_with_stdio(false)
#define File(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout)
using namespace std;
typedef long long ll;
template <typename T> inline void read(T &x) {
T f = 1; x = 0; char c;
for (c = getchar(); !isdigit(c); c = getchar()) if (c == '-') f = -1;
for ( ; isdigit(c); c = getchar()) x = x * 10 + (c ^ 48);
x *= f;
}
map <string, int> mp;
struct edge {
int u, v, w;
friend bool operator < (edge x, edge y) { return x.w < y.w; }
} e[N*N], dp[N];
int n(1), m, k, cnt, ans;
int g[N][N], fa[N], mne[N], pos[N];
bool con[N][N];
string s;
int cal() { return mp.find(s) == mp.end() ? mp[s] = ++n : mp[s]; }
int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
void Kruskal() {
sort(e + 1, e + 1 + m);
for (int i = 1; i <= n; ++i) fa[i] = i;
for (int i = 1; i <= m; ++i) {
int u = e[i].u, v = e[i].v, w = e[i].w;
if (u == 1 || v == 1) continue;
int f1 = find(u), f2 = find(v);
if (f1 == f2) continue;
fa[f2] = f1; con[u][v] = con[v][u] = 1;
ans += w;
}
}
void link() {
int now;
memset(mne, 10, sizeof mne);
for (int i = 2; i <= n; ++i)
if (g[1][i] != -1) {
now = find(i);
if (mne[now] > g[1][i])
pos[now] = i, mne[now] = g[1][i];
}
for (int i = 1; i <= n; ++i)
if (mne[i] != mne[0]) {
cnt++;
con[1][pos[i]] = con[pos[i]][1] = 1;
ans += g[1][pos[i]];
}
}
void dfs(int u, int _fa) {
for (int i = 2; i <= n; ++i)
if (con[i][u] && i != _fa) {
if (dp[i].w == -1) {
if (g[u][i] < dp[u].w) dp[i] = dp[u];
else dp[i].u = u, dp[i].v = i, dp[i].w = g[u][i];
}
dfs(i, u);
}
}
void solve() {
for (int i = cnt + 1; i <= k; ++i) {
memset(dp, -1, sizeof dp);
for (int j = 2; j <= n; ++j)
if (con[1][j]) dp[i].w = -INF;
dp[1].w = -INF; dfs(1, -1);
int now = 0, minn = INF;
for (int j = 2; j <= n; ++j)
if (g[1][j] != -1 && g[1][j] - dp[j].w < minn)
minn = g[1][j] - dp[j].w, now = j;
if (minn >= 0) break;
int u = dp[now].u, v = dp[now].v;
con[1][now] = con[now][1] = 1;
con[u][v] = con[v][u] = 0;
ans += minn;
}
}
int main() {
int u, v, w;
mp["Park"] = 1;
read(m);
memset(g, -1, sizeof g);
for (int i = 1; i <= m; ++i) {
cin >> s; e[i].u = u = cal();
cin >> s; e[i].v = v = cal();
read(e[i].w); w = e[i].w;
g[u][v] = g[v][u] = (g[u][v] == -1) ? w : min(g[u][v], w);
}
read(k);
Kruskal();
link();
solve();
printf("Total miles driven: %d\n", ans);
return 0;
}
参考
- IOI2004国家集训队论文《最小生成树问题的拓展》 ——王汀
- 《算法竞赛进阶指南》 ——李煜东
- 代码