50212228海岛帝国:LYF的太空运输站
【试题描述】
最近,“购物券”WHT在“药师傅”帝国资源大会上提出了“SSTS”太空运输站计划。由于恐怖分子前些日子刚猖狂完,炸毁高楼无数,YSF不得不执行 WHT丧心病狂的计划,“演员”KLINT(众所周知,又一大土豪同学)捐赠了众多资源,和高级技术。太空运输站建成了,YSF任命LYF为站长,LJX 为副站长。第一波运输计划开始了!可是,当运输军队到达中转站金星时,遭到了盗取新技术的恐怖分子的袭击。由于没有足够的兵力,整个舰队全军覆没,LYF 损失惨重,恼羞成怒,随即决定让YSM和LJX调用一半星际舰队。可恐怖分子太强,再次损失惨重。YSF无奈,决定给“过路费”。但YSF是个贪财的人, 所以,YSF想让给的钱最少。他把这个难题交给了LYF,LYF又把这个任务交给了LJX,所以请你帮帮可怜的LYF,帮他编一个程序。另外,悬赏 10000000000000000000000000000000000$,所以赶快做吧!
【输入要求】
* 第一行:两个整数:n表示有n个城市,m表示有m条道路。
* 接下来的m行,每行三个整数:a,b,c表示从a星到达b星的路花费是c
【输出要求】
输出需要钱数最少方案
【输入实例】
6 9
2 4 11
3 5 13
4 6 3
5 6 4
2 3 6
4 5 7
1 2 1
3 4 9
1 3 2
【输出实例】
19
【其他说明】
n<=15,m<=18
40ms够了吧?
【试题分析】
仔细思考这一题,其实就是让银子用的越少越好。换句话说,就是让最少的边让图连接,让多余的边去掉。而定义是:如果一个连通无向图不包含回路,那么,就完全可以说这就是一棵树。在这里,我们讨论的是图的最小生成树。那么,问题来了:怎么选出这N-1条边,让边的总长度之和最短呢?我们一下就可以想到:我们自然可以选择最短的边,然后再选择次短的边……直到选完了所有N-1条边为止。我们可以先排序,这是最显而易见的方法。依次选择,直到选择了N-1条边让图联通为止。比较难于实现的是判断两个顶点是否有连通,否则就产生了回路,就不是树了。我们可以用以前的DFS或BFS来解决这个问题,但是,效率好像很低?上面要求40ms之内搞定,所以,还有一种更好的方法——并查集。将所有顶点放在一个并查集中。只需判断两个点是否在一个集合里(即是否拥有共同的祖先)。这样操作的时间复杂度仅为O(logN)。
----------------------------------------------------------【以下为转载内容】
这个算法叫Kruskal算法,求加权连通图的最小生成树的算法。kruskal算法总共选择n- 1条边,(共n个点)所使用的贪婪准则是:从剩下的边中选择一条不会产生环路的 具有最小耗费的边加入已选择的边的集合中。注意到所选取的边若产生环路则不可能形成一棵生成树。kruskal算法分e 步,其中e 是网络中边的数目。按耗费递增的顺序来考虑这e 条边,每次考虑一条边。当考虑某条边时,若将其加入到已选边的集合中会出现环路,则将其抛弃,否则,将它选入。
假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的 过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶 点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。
C语言代码:
#include "stdio.h"
#include "stdlib.h"
struct edge
{
int m;
int n;
int d;
}a[];
int cmp(const void *a,const void *b)//按升序排列
{
return((struct edge*)a)->d - ((struct edge*)b)->d;
}
int main(void)
{
inti,n,t,num,min,k,g,x[];
printf("请输入顶点的个数:");
scanf("%d",&n);
t = n * ( n - ) / ;
for(i=;i<=n;i++)
x[i]=i;
printf("请输入每条边的起始端点、权值:/n");
for(i=;i<t;i++)
scanf("%d%d%d",&a[i].m,&a[i].n,&a[i].d);//输入每条边的权值
qsort(a,t,sizeof(a[]),cmp);
min=num=;
for(i=;i<t && num < n-;i++)
{
for(k=a[i].m;x[k]!=k;k=x[k])//判断线段的起始点所在的集合
x[k]=x[x[k]];
for(g=a[i].n;x[g]!=g;g=x[g])//判断线段的终点所在的集合
x[g]=x[x[g]];
if(k!=g)//如果线段的两个端点所在的集合不一样
{
x[g]=k;
min+=a[i].d;
num++;
printf("最小生成树中加入边:%d%d/n",a[i].m,a[i].n);
}
}
printf("最小生成树的权值为:%d/n",min);
system("pause");
return ;
}
kruskal算法的基本思想:
1.首先将G的n个顶点看成n个孤立的连通分支(n个孤立点)并将所有的边按权从小大排序。
2.按照边权值递增顺序,如果加入边后存在圈则这条边不加,直到形成连通图对2的解释:如果加入边的两个端点位于不同的连通支,那么这条边可以顺利加入而不会形成圈
本例中用到的图:
这个算法执行的过程就是按照规定一个个连通支合并的过程,使最后只剩一个连通支。
【以上内容为转载】
【代码】
#include<iostream>
using namespace std;
int dis[],book[]={};
int h[],pos[],size;
void swap(int x,int y)
{
int t;
t=h[x];
h[x]=h[y];
h[y]=t;
t=pos[h[x]];
pos[h[x]]=pos[h[y]];
pos[h[y]]=t;
return ;
}
void siftdown(int i)
{
int t,flag=;
while(i*<=size&&flag==)
{
if(dis[h[i]]>dis[h[i*]]) t=i*;
else t=i;
if(i*+<=size)
if(dis[h[t]]>dis[h[i*+]]) t=i*+;
if(t!=i)
{
swap(t,i);
i=t;
}
else flag=;
}
return ;
}
void siftup(int i)
{
int flag=;
if(i==) return ;
while(i!=&&flag==)
{
if(dis[h[i]]<dis[h[i/]]) swap(i,i/);
else flag=;
i/=;
}
return ;
}
int pop()
{
int t;
t=h[];
pos[t]=;
h[]=h[size];
pos[h[]]=;
size--;
siftdown();
return t;
}
int main()
{
int n,m,i,j,k;
int u[],v[],w[],first[],next[];
int inf=;
int count=,sum=;
scanf("%d%d",&n,&m);
for(i=;i<=m;i++) scanf("%d%d%d",&u[i],&v[i],&w[i]);
for(i=m+;i<=*m;i++)
{
u[i]=v[i-m];
v[i]=u[i-m];
w[i]=w[i-m];
}
for(i=;i<=n;i++) first[i]=-;
for(i=;i<=*m;i++)
{
next[i]=first[u[i]];
first[u[i]]=i;
}
book[]=;
count++;
dis[]=;
for(i=;i<=n;i++) dis[i]=inf;
k=first[];
while(k!=-)
{
dis[v[k]]=w[k];
k=next[k];
}
size=n;
for(i=;i<=size;i++)
{
h[i]=i;
pos[i]=i;
}
for(i=size/;i>=;i--)
{
siftdown(i);
}
pop();
while(count<n)
{
j=pop();
book[j]=;
count++;
sum+=dis[j];
k=first[j];
while(k!=-)
{
if(book[v[k]]==&&dis[v[k]]>w[k])
{
dis[v[k]]=w[k];
siftup(pos[v[k]]);
}
k=next[k];
}
}
printf("%d",sum);
}