Agri-Net (POJ 1258)

来源:互联网 发布:查看mac激活时间 编辑:程序博客网 时间:2024/04/29 01:41

Description

Farmer John has been elected mayor of his town! One of his campaign promises was to bring internet connectivity to all farms in the area. He needs your help, of course. 
Farmer John ordered a high speed connection for his farm and is going to share his connectivity with the other farmers. To minimize cost, he wants to lay the minimum amount of optical fiber to connect his farm to all the other farms. 
Given a list of how much fiber it takes to connect each pair of farms, you must find the minimum amount of fiber needed to connect them all together. Each farm must connect to some other farm such that a packet can flow from any one farm to any other farm. 
The distance between any two farms will not exceed 100,000. 

Input

The input includes several cases. For each case, the first line contains the number of farms, N (3 <= N <= 100). The following lines contain the N x N conectivity matrix, where each element shows the distance from on farm to another. Logically, they are N lines of N space-separated integers. Physically, they are limited in length to 80 characters, so some lines continue onto others. Of course, the diagonal will be 0, since the distance from farm i to itself is not interesting for this problem.

Output

For each case, output a single integer length that is the sum of the minimum length of fiber required to connect the entire set of farms.

Sample Input

40 4 9 214 0 8 179 8 0 1621 17 16 0

Sample Output

28

这题是求解最小生成树权值之和的模板题。最常用的算法是Prim和Kruskal。其中,Prim从任一节点出发,不断扩展,适用于密集图;而Kruskal将所有边依权值序从小到大放入,在过程中判断是否产生回路,适用于稀疏图。

根据样例输入,对Prim算法实现的生成过程进行简要的说明。最初,用于为边权进行排序的优先队列pq中只有一个元素(0, 0)。经过一些条件判断,(0, 0)被从pq中提取出,(0, 0)的终点0被记作已经过。随后考虑从该终点0出发的边,用各边的路径长度更新vDist数组,直观上理解为从已经过的点(在建生成树)出发,到达未经过的点的最短路径长度。该步骤完成后,将点0出发的边 (1, 4),(2, 9),(3, 21)推入pq。进入下一次循环。

下一次循环中,首先,1被纳入在建生成树,1被标记为已经过。随后,(1, 4)被最早推出pq,因其在pq中具有最小的权值。根据类似的过程,(2, 8)和(3, 17)被推入pq。此时pq中包含四个元素:(2, 9), (3, 21), (2, 8), (3, 17),其中前面来个是从0点出发,后面两个是从1点出发。由pq的排序规则,最早出列的是(2, 8)。这是满足我们对该算法的理解的,因为(2, 8)是在建生成树与未被纳入生成树的点所发生的最短连接。

#include<iostream>#include<algorithm>#include<vector>#include<queue>#include<cstring>using namespace std;//by郭炜老师;//最小生成树模板题,给定图的邻接矩阵,求最小生成树的总权值;//优先队列实现Prim+堆//const int INF = 1 << 30;struct Edge{    int v; //端点,另一端点已知;    int w; //权值;    Edge (int vv, int ww): v(vv), w(ww){};    bool operator < (const Edge & e)const    {        return w > e.w; //权值越小,在队列中越优先;    }};vector<vector<Edge> > G(110); //图的邻接表;int HeapPrim( const vector<vector<Edge> > & G, int n){//G是邻接表,n是顶点数目,返回值是最小生成树的权值和;    //int i, j, k;    Edge xDist(0,0);    priority_queue<Edge> pq; //存放未经过顶点及其到在建生成树的距离;    vector<int> vUsed(n); //标记顶点是否已经被加入最小生成树;    int nDoneNum = 0; //已经被加入最小生成树的顶点数目;    for (int i = 0; i < n; i++)    {        vUsed[i] = 0;    }    int nTotalW = 0; //最小生成树总权值;    pq.push(Edge(0, 0)); //开始只有顶点0,它到最小生成树距离0;    while (nDoneNum < n && !pq.empty())    { //do-while,先执行后判断;        do{//每次从队列里面取离在建生成树最近的点;            xDist = pq.top();            pq.pop();        }while(vUsed[xDist.v] == 1 && !pq.empty()); //如果xDist.v未被到达,则停止循环;        if (vUsed[xDist.v] == 0) //作为被pop出来的第一个,它具有最小的权值,因而被加入到在建最小生成树中;        {            nTotalW += xDist.w;            vUsed[xDist.v] = 1;            nDoneNum++;            for (int i = 0; i < G[xDist.v].size(); i++)            { //更新新加入的邻点;                int k = G[xDist.v][i].v;                int w = G[xDist.v][i].w;                if (vUsed[k] == 0) //只记录那些连接了当前所在点未到达点的边的信息,推入pq中;                {                    pq.push(Edge(k, w));                }            }        }    }    if (nDoneNum < n)        return -1; //图不连通    return nTotalW;}int main(){    int n;    while (cin >> n)    {        for (int i = 0; i < n; i++)            G[i].clear();        for (int i = 0; i < n; i++)        {            for (int j = 0; j < n; j++)            {                int w;                cin >> w;                G[i].push_back(Edge(j, w));            }        }        cout << HeapPrim(G, n) << endl;    }}

也可以使用Kruskal算法进行求解。

#include<iostream>#include<algorithm>#include<vector>#include<queue>#include<cstring>using namespace std;//BY 郭炜老师;//Kruskal算法的原理是简单的:按权值从小到大取边,满足不构成回路即可;//相比Prim算法,Kruskal算法在代码实现上要更简单;//使用了并查集;struct Edge{    int s, e, w; //起点,终点,权;    Edge(int ss, int ee, int ww):s(ss), e(ee), w(ww){};    bool operator < (const Edge & e1)const{ //按权值排序;        return w < e1.w;    }};vector<Edge> edges;vector<int> parent; //每个节点的父节点的编号;int GetRoot(int a){    if (parent[a] == a) //a是根;        return a;    parent[a] = GetRoot(parent[a]);    return parent[a];}void Merge(int a, int b){    int p1 = GetRoot(a);    int p2 = GetRoot(b);    if (p1 == p2)        return;    parent[p2] = p1;}int main(){    int n;    while (cin >> n)    {        parent.clear();        edges.clear();        for (int i = 0; i < n; i++) //初始状态,每个节点自成一棵树;            parent.push_back(i);        for (int i = 0; i < n; i++)        {            for (int j = 0; j < n; j++)            {                int w;                cin >> w;                edges.push_back(Edge(i, j, w));            }        }        sort(edges.begin(),edges.end());        int done = 0; //记录已完成的边;        int totalLen = 0;        for (int i = 0; i < edges.size(); i++)        {            if (GetRoot(edges[i].s) != GetRoot(edges[i].e))            { //这一条边的两个端点还未连通,可以放心地合并;                Merge(edges[i].s, edges[i].e); //表示该边被选择,两端点被连通;                done++;                totalLen += edges[i].w;            }            if (done == n - 1)                break;        }        cout << totalLen << endl;    }}




0 0