[POJ 1639] 单度限制最小生成树

题目大意:

 有一个公园,最多只能允许有K条路通向公园,问在这个限制条件下,所能形成的最小生成树。


资料:

IOI2004国家集训队论文--王汀《最小生成树问题的扩展》

http://wenku.baidu.com/view/41800d66ddccda38376bafac.html


度限制最小生成树的算法的关键是动态规划来实现更新每一个点到根节点的最大边。(具体实现见代码)


/*
ID: wuqi9395@126.com
PROG:
LANG: C++
度限制最小的生成树:
1. 去掉根节点,生成一个森林。
2. 将每个子树分别于根节点相连(取最小的边)。生成一个m度最小生成树
3. 迭代(k - m)次,每次连接一条没用过的与根节点相连的边,形成一个环,去掉其中不与根节点相连的边

在更新每一个点到根节点的最大值时,可以采用动态规划的算法 mx[v] = max(mx[pre[v]], edge[v][pre[v]])
复杂度 O(VlogV + k * n)
*/
#include<map>
#include<set>
#include<queue>
#include<stack>
#include<cmath>
#include<cstdio>
#include<vector>
#include<string>
#include<fstream>
#include<cstring>
#include<ctype.h>
#include<iostream>
#include<algorithm>
#define maxn 105
#define maxm 100005
#define INF (1<<30)
#define PI acos(-1.0)
#define mem(a, b) memset(a, b, sizeof(a))
#define For(i, n) for (int i = 0; i < n; i++)
typedef long long ll;
using namespace std;
int m, n, head[maxn], cnt, k, vis[maxn];
int cnt_block, ans, belong[maxn], pre[maxn], dis[maxn], use[maxn][maxn], link[maxn], point[maxn];
//cnt_block记录连通块的数量,belong[]记录每个点属于第几个连通分量, pre[]记录生成森林时的前驱, dis[]记录每个点到该连通分量根节点距离
//use[][]记录该条边是否使用,link[]记录每一连通分量到根节点的最小距离,point[]记录每个连通分量中与根节点相连的点
vector<int> block[maxn];
map<string, int> mp;
struct node {
   int v, w, next;
}g[maxm];
struct Edge {
    int u, v, w;
    Edge() {}
    Edge(int a, int b, int c) {
        u = a, v = b, w = c;
    }
    void init() { w = 0; }
    bool operator > (const Edge & a) const {
        return w > a.w;
    }
}mx[maxn];
int getId(char s[]) {
    if (mp.find(s) == mp.end()) mp[s] = ++n;
    else return mp[s];
    return n;
}
void addedge(int u, int v, int w) {
    g[cnt].v = v;
    g[cnt].w = w;
    g[cnt].next = head[u];
    head[u] = cnt++;
}
void dfs(int v) {    //将图分成若干连通块
    vis[v] = 1;
    belong[v] = cnt_block;
    block[cnt_block].push_back(v);
    for (int i = head[v]; i != -1; i = g[i].next)
        if (!vis[g[i].v]) dfs(g[i].v);
}
void prim(int cur) {
    dis[block[cur][0]] = 0;
    for (int i = 1; i <= block[cur].size(); i++) {
        int mn = INF, k = -1;
        for (int j = 0; j < block[cur].size(); j++) {
            int v = block[cur][j];
            if (pre[v] != -1 && dis[v] < mn) {
                mn = dis[v];
                k = v;
            }
        }
        if (k != -1) {
            ans += dis[k];
            use[pre[k]][k] = use[k][pre[k]] = 1;
            pre[k] = -1;
            for (int i = head[k]; i != -1; i = g[i].next) if (pre[g[i].v] != -1 && dis[g[i].v] > g[i].w) {
                dis[g[i].v] = g[i].w;
                pre[g[i].v] = k;
            }
        }
    }
}
void MdegreeMST() {
    mem(vis, 0);
    vis[1] = 1;
    cnt_block = 0, ans = 0;
    for (int i = 2; i <= n; i++) if (!vis[i]) {     //求有多少连通分量
        cnt_block++, block[cnt_block].clear();
        dfs(i);
    }

    pre[1] = -1;
    mem(use, 0); mem(pre, 0);
    for (int i = 1; i <= n; i++) dis[i] = link[i] = INF;    //生成最小森林
    for (int i = 1; i <= cnt_block; i++) prim(i);
    for (int i = head[1]; i != -1; i = g[i].next) if (link[belong[g[i].v]] > g[i].w) { //将森林生成一个M度树
        link[belong[g[i].v]] = g[i].w;
        point[belong[g[i].v]] = g[i].v;
    }
    for (int i = 1; i <= cnt_block; i++) {
        ans += link[i];
        use[1][point[i]] = use[point[i]][1] = 1;
    }
}
void getMax(int v, int fa, int w) {     //更新树上每一点到根节点的最大距离
    pre[v] = fa;
    Edge tmp(v, fa, w);
    if (tmp > mx[fa]) mx[v] = tmp;
    else mx[v] = mx[fa];
    for (int i = head[v]; i != -1; i = g[i].next) if (use[v][g[i].v] && g[i].v != fa)   //不能产生环路
        getMax(g[i].v, v, g[i].w);
}
void solve() {
    int degree = cnt_block;
    for (int i = 0; i <= n; i++) mx[i].init();
    getMax(1, 0, 0);    //从根节点出发更新每个点到根节点的最大边
    while (degree < k) {
        int mn = 0, pos = 0, w;
        for (int i = head[1]; i != -1; i = g[i].next) if (!use[1][g[i].v] && mx[g[i].v].w - g[i].w > mn) {
            mn = mx[g[i].v].w - g[i].w;
            pos = g[i].v, w = g[i].w;
        }
        if (!pos) break;
        ans -= mn;
        degree++;
        use[pos][1] = use[1][pos] = 1;
        use[mx[pos].u][mx[pos].v] = use[mx[pos].v][mx[pos].u] = 0;
        getMax(pos, 1, w);
    }
    printf("Total miles driven: %d\n", ans);
}
void init() {
    mem(head, -1);
    cnt = 0, n = 1;
    char s1[55], s2[55];
    scanf("%d", &m);
    mp.clear();
    mp["Park"] = 1;
    int w;
    for (int i = 0; i < m; i++) {
        scanf("%s%s%d", s1, s2, &w);
        int u = getId(s1), v = getId(s2);
        addedge(u, v, w);
        addedge(v, u, w);
    }
    scanf("%d", &k);
}
int main ()
{
    init();
    MdegreeMST();
    solve();
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值