uva1151

题目描述:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=36013

/*
考察点:kruskal算法
算法步骤:
1:在不购买任何子网的情况下,利用kruskal算法求出最小生成树
2:用二进制法枚举所有子网可能的购买方案
3:对每一种子网购买方案,还是利用kruskal算法求出最小生成树。
    与第一次用kruskal算法不同的是,这次是基于原图已经求出来的
    最小生成树和已经购买的子网二者所包含的边集合来求最小生成树
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>

using namespace std;
const int maxn = 1000 + 5;
const int maxNet = 10;

int cityNum, netNum, pa[maxn], ans, netCost[maxNet];
int x[maxn], y[maxn];

struct Edge {
    int u, v, val;

    Edge(int u = 0, int v = 0, int val = 0) : u(u), v(v), val(val) {}

    bool operator < (const Edge & rhs) const {
        return val < rhs.val;
    }
};

vector<Edge> all, need;
vector<int> citySub[maxNet];  //citySub[i]表示第i个子网里面的城市编号

void readInput() {
    int n, id;
    scanf("%d%d", &cityNum, &netNum);

    for(int i = 0; i < netNum; i++) {
        scanf("%d%d", &n, &netCost[i]);
        citySub[i].clear();
        while(n--) {
            scanf("%d", &id);
            citySub[i].push_back(id - 1);
        }
    }

    for(int i = 0; i < cityNum; i++)   scanf("%d%d", &x[i], &y[i]);

    for(int i = 0; i < cityNum; i++)   //先算出所有城市之间所有可能的路径
    for(int j = i + 1; j < cityNum; j++) {
        int v = (x[i]-x[j])*(x[i]-x[j]) + (y[i]-y[j])*(y[i]-y[j]);  //欧几里德距离
        all.push_back(Edge(i, j, v));   //将原图所有边存储进去
    }
}

int UFind(int u) {
    return pa[u] == u ? u : UFind(pa[u]);
}

int kruskal(int cnt, const vector<Edge> &all, vector<Edge> &need) { //kruskal算法,基于所有边(存储在all中),将求出来的最小生成树存储进need中
    if(cnt == 1)  return 0;
    int an = 0;
    need.clear();
    for(int i = 0; i < all.size(); i++) {
        int u = UFind(all[i].u);    int v = UFind(all[i].v);
        if(u != v) {
            pa[u] = v;
            an += all[i].val;
            need.push_back(all[i]);
            if(--cnt == 1)    break;
        }
    }
    return an;
}

void binEnum() {    
    for(int i = 0; i < (1 << netNum); i++) {    //这一整个循环就是二进制枚举
        for(int i = 0; i < cityNum; i++)    pa[i] = i;
        int c = 0;  int cnt = cityNum;
        for(int j = 0; j < netNum; j++)
            if(i & (1 << j)) {
                c += netCost[j];    //求出购买子网的成本
                for(int k = 1; k < citySub[j].size(); k++) {    //将该子网内的所有城市连接在一起
                    int u = UFind(citySub[j][k]);     int v = UFind(citySub[j][0]);
                    if(u != v) {
                        pa[u] = v;
                        cnt--;
                    }
                }
            }

        vector<Edge> vec;
        ans = min(ans, c + kruskal(cnt, need, vec));    //每次枚举一种方案就计算最便宜的方案
    }
}

int main()
{
    //freopen("input.txt", "r", stdin);
    int T;
    scanf("%d", &T);
    while(T--) {
        all.clear();
        readInput();    //读入数据

        for(int i = 0; i < cityNum; i++)   pa[i] = i;
        sort(all.begin(), all.end());
        ans = kruskal(cityNum, all, need);  //在不购买任何子网的情况下求出来的最小生成树

        binEnum();      //二进制枚举法枚举所有子网购买方案组合,并将其与已经求出来的原图最小生成树
                                //作为基础,再次求最小生成树。
        printf("%d\n", ans);
        if(T) printf("\n");
    }
    return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值