最小度限制生成树学习笔记

题意

给定一个 \(n\) 个点 \(m\) 条边的无向图,求出无向图的一棵最小生成树,满足给定节点的度数不超过 \(k\)

算法

设给定的节点为 \(v_0\)

  1. 先求出最小生成树,将与 \(v_0\) 相连的边删去,这样就会产生一些联通分量,设有 \(m\) 个联通分量。
  2. 我们需要再将 \(v_0\) 与这 \(m\) 个联通分量连 \(k\) 条边,使 \(v_0\) 的度数为 \(k\) 且保证图连通。
  3. 所以当 \(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\) 相连并把被替换的边删去即可求出答案。

例题

POJ 1639

我们将字符串用 \(\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;
}

参考

posted @ 2020-01-16 19:06  小蒟蒻hlw  阅读(384)  评论(0)    收藏  举报