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

题意

给定一个 \(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;
}

参考

猜你喜欢

转载自www.cnblogs.com/hlw1/p/12202824.html