POJ - 2485 Highways(prim算法,Kruskal算法)

Time Limit: 1000MS Memory Limit: 65536KB 64bit IO Format: %I64d & %I64u

[]   [Go Back]   [Status]  

Description

The island nation of Flatopia is perfectly flat. Unfortunately, Flatopia has no public highways. So the traffic is difficult in Flatopia. The Flatopian government is aware of this problem. They're planning to build some highways so that it will be possible to drive between any pair of towns without leaving the highway system. 

Flatopian towns are numbered from 1 to N. Each highway connects exactly two towns. All highways follow straight lines. All highways can be used in both directions. Highways can freely cross each other, but a driver can only switch between highways at a town that is located at the end of both highways. 

The Flatopian government wants to minimize the length of the longest highway to be built. However, they want to guarantee that every town is highway-reachable from every other town.

Input

The first line of input is an integer T, which tells how many test cases followed. 
The first line of each case is an integer N (3 <= N <= 500), which is the number of villages. Then come N lines, the i-th of which contains N integers, and the j-th of these N integers is the distance (the distance should be an integer within [1, 65536]) between village i and village j. There is an empty line after each test case.

Output

For each test case, you should output a line contains an integer, which is the length of the longest road to be built such that all the villages are connected, and this value is minimum.

Sample Input

1

3
0 990 692
990 0 179
692 179 0

Sample Output

692

Hint

Huge input,scanf is recommended.

Source

POJ Contest,Author:Mathematica@ZSU






prim算法求最小生成树。裸prim。代码:
#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=505;
bool visit[maxn];//标记数组,没有加入到树中时为false,加入了为true
int map[maxn][maxn],dist[maxn];//dist[maxn]用以记录当前树到各个顶点的最小距离(它会被不断的更新,加入一个顶点更新一次)
int n;
int prim()
{
  int i,j,maxm=0,v;
  for(i=0;i<n;i++)//计算只有一个顶点时的distan[i]
  dist[i]=map[0][i];
  memset(visit,false,sizeof(visit));
  visit[0]=true;//将第一个顶点加入树中
  for(i=1;i<n;i++)
  {
      int mini=0xffffff;
      for(j=0;j<n;j++)//找最小的边
          if(visit[j]==false&&dist[j]<mini)//找出没有在当前树中且权值最小的点
          {
              mini=dist[j];
              v=j;
          }
      visit[v]=true;//标记顶点v,加入生成树中
      maxm=maxm>mini?maxm:mini;
      for(j=0;j<n;j++)//将生成树的权值更新
          if(visit[j]==false&&dist[j]>map[j][v])
          dist[j]=map[j][v];//distan[i]中始终放生成树到顶点i的最小权值
  }
  return maxm;
}
int main()
{
    int t,i,j;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        for(i=0;i<n;i++)
        for(j=0;j<n;j++)
        scanf("%d",&map[i][j]);
        printf("%d\n",prim());
    }
    return 0;
}



总结:读懂题意。题意略坑。要求求最小生成树的最大边。(不是求和)






Kruskal算法,用到了并查集。代码:(636 KB  407 ms)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=505;
int n,q;
int fa[maxn];
struct Map
{
    int x,y,len;
}map[maxn*maxn];
bool cmp(Map x,Map y)
{
    return x.len<y.len;
}
int find(int x)
{
    while(x!=fa[x])
    x=fa[x];
    return x;
}
void kruskal()
{
    int i,a,b,maxm=0;
    sort(map,map+q,cmp);//先给所有边按从小到大排序,每次读入最小的边,且保证新读入的的边能与之前的边构成树
    for(i=1;i<=n;i++)
    fa[i]=i;//初始化所有节点的父节点为自身
    for(i=0;i<q;i++)
    {
        a=find(map[i].x);
        b=find(map[i].y);
        if(a!=b)
        {
//            maxm=maxm>p[i].len?maxm:p[i].len;
            //if(p[i].len>maxm)
                maxm=map[i].len;
            fa[b]=a;
//            printf("%d\n",maxm);
        }
    }
    printf("%d\n",maxm);
}
void getmap()
{
  //int i,j,q=0;//这里重复定义了q,导致一直wa。如果这里定义q,那么main函数外的q将始终为0。警惕!!!!!!
惊恐
  q=0;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
  for(int j=1;j<=n;j++)
  {
      scanf("%d",&map[q].len);
      map[q].x=i;map[q].y=j;
      q++;
  }
}


int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        getmap();
        kruskal();
    }
    return 0;
}



优化后的Kruskal算法:(396 KB  282 ms)
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn=505;
int n,q;
int fa[maxn];
struct Map
{
    int x,y,len;
}map[maxn*maxn/2];//空间只要不到一半
bool cmp(Map x,Map y)
{
    return x.len<y.len;
}
int find(int x)
{
    while(x!=fa[x])
    x=fa[x];
    return x;
}
void kruskal()
{
    int i,a,b,maxm=0;
    sort(map,map+q,cmp);//时间也得到优化
    for(i=1;i<=n;i++)
    fa[i]=i;
    for(i=0;i<q;i++)
    {
        a=find(map[i].x);
        b=find(map[i].y);
        if(a!=b)
        {
            maxm=map[i].len;
            fa[b]=a;
        }
    }
    printf("%d\n",maxm);
}
void getmap()
{
  //int i,j,q=0;//这里重复定义了q,导致一直wa。如果这里定义q,那么main函数外的q将始终为0
  q=0;
  scanf("%d",&n);
  for(int i=1;i<=n;i++)
  for(int j=1;j<=n;j++)
  {
      int temp;
      if(i<j)//只取上三角,时间,空间都得到优化
      {
          scanf("%d",&map[q].len);
          map[q].x=i;map[q].y=j;
          q++;
      }
      else scanf("%d",&temp);
  }
}


int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        getmap();
        kruskal();
    }
    return 0;
}



附录:
Kruskal算法证明


  易证,对于一个无向加权连通图,总是存在一棵或以上的有限课生成树,而这些生成树中肯定存在至少一棵最小生成树。下面证明Kruskal算法构造的生成树是这些最小生成树中的一棵。
  设T为Kruskal算法构造出的生成树,U是G的最小生成树。如果T==U那么证明结束。如果T != U,我们就需要证明T和U的构造代价相同。由于T != U,所以一定存在k > 0条边存在于T中,却不在U中。接下来,我们做k次变换,每次从T中取出一条不在U中的边放入U,然后删除U一条不在T中的边,最后使T和U的边集相同。每次变换中,把T中的一条边e加入U,同时删除U中的一条边f。e、f按如下规则选取:a). e是在T中却不在U中的边的最小的一条边;b). e加入U后,肯定构成唯一的一个环路,令f是这个环路中的一条边,但不在T中。f一定存在,因为T中没有环路。


  这样的一次变换后,U仍然是一棵生成树。
  我们假设e权值小于f,这样变换后U的代价一定小于变换前U的代价,而这和我们之前假设U是最小生成树矛盾,因此e权值不小于f。
  再假设e权值大于f。由于f权值小于e,由Kruskal算法知,f在e之前从E中取出,但被舍弃了。一定是由于和权值小于等于f的边构成了环路。但是T中权值小于等于f(小于e)的边一定存在于U中,而f在U中却没有和它们构成环路,又推出矛盾。所以e权值不大于f。于是e权值等于f。
  这样,每次变换后U的代价都不变,所以K次变换后,U和T的边集相同,且代价相同,这样就证明了T也是最小生成树。由证明过程可以知道,最小生成树可以不是唯一的。


无向图G的所有最小生成树的边权集合相同


3.简单证明prim算法

反证法:假设prim生成的不是最小生成树

1).设prim生成的树为G0

2).假设存在Gmin使得cost(Gmin)<cost(G0)   则在Gmin中存在<u,v>不属于G0

3).将<u,v>加入G0中可得一个环,且<u,v>不是该环的最长边(这是因为<u,v>∈Gmin)

4).这与prim每次生成最短边矛盾

5).故假设不成立,命题得证.


 








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值