Connect the Cities
Description
In 2100, since the sea level rise, most of the cities disappear. Though some survived cities are still connected with others, but most of them become disconnected. The government wants to build some roads to connect all of these cities again, but they don’t want to take too much money.
Input
The first line contains the number of test cases.
Each test case starts with three integers: n, m and k. n (3 <= n <=500) stands for the number of survived cities, m (0 <= m <= 25000) stands for the number of roads you can choose to connect the cities and k (0 <= k <= 100) stands for the number of still connected cities.
To make it easy, the cities are signed from 1 to n.
Then follow m lines, each contains three integers p, q and c (0 <= c <= 1000), means it takes c to connect p and q.
Then follow k lines, each line starts with an integer t (2 <= t <= n) stands for the number of this connected cities. Then t integers follow stands for the id of these cities.
Output
For each case, output the least money you need to take, if it’s impossible, just output -1.
Sample Input
1
6 4 3
1 4 2
2 6 1
2 3 5
3 4 33
2 1 2
2 1 3
3 4 5 6
Sample Output
1
题解:
这是一道典型的最小生成树的问题,最小生成树有两种算法,Prim和Kruskal在这道题里面是都适用的,正好趁着这道题总结一下这两种算法。
Prim算法:
基本原理:不断的将节点加入到一个树上(used数组标志),每次找到的下一个点都是距离当前树权值最小的节点;
适用题型:注意到Prim算法以节点为关注点,所以对于节点数较少,边数较多的密集图,应该使用Prim算法;
Kruskal算法:
基本原理:贪心的每次挑选出当前边集中最小的边,将边加入到树上,生成一个联通子图,但注意已生成的树上不能出现环,这点利用并查集实现;
适用题型:边较少的稀疏图;
对于这道题目, (3 <= n <=500)(0 <= m <= 25000),是一个 稠密图,按理利用Prim会更好一点,但是注意每次都会有K组的节点是相互连通的,对于Prim算法来说将这些信息保存将会用到O(N^2)的复杂度。而对于Kruskal来说加边就很方便,只需要将他们生成一个联通子图。所以这道题两种方法的时间相差不大,但是有助于理解算法的实现与优化。
代码实现:
Prime:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define MAX_N 510
#define MAX_M 25010
#define MAX_K 110
#define LL long long
#define INF 0x7fffffff
using namespace std;
int N,M,K;
int res,T;
int cost[MAX_N][MAX_N];
int mincost[MAX_N];
int Left[MAX_N];
bool used[MAX_N];
void Prim();
void Check();
int main()
{
scanf("%d",&T);
while( T-- ){
scanf("%d%d%d",&N,&M,&K);
for( int i = 1; i <= N; i++ ){
for( int j = 1; j <= N; j++ ){
cost[i][j] = INF;
}
cost[i][i] = 0;
mincost[i] = INF;
used[i] = false;
}
int a,b,c;
int num;
for( int i = 0; i < M; i++ ){
scanf("%d%d%d",&a,&b,&c);
cost[a][b] = min(cost[a][b],c);
cost[b][a] = min(cost[b][a],c);
}
for( int i = 0; i < K; i++ ){
scanf("%d",&num);
for( int j = 0; j < num; j++ ){
scanf("%d",&Left[j]);
}
for( int j = 0; j < num; j++ ){
for( int t = 0; t < num; t++ ){
cost[Left[t]][Left[j]] = 0;
//压坏算法的最后一根稻草,TLE了无数次,只做了一个这么小的优化就过了。。。
//cost[Left[j]][Left[t]] = 0;
}
}
}
Prim();
printf("%d\n",res);
}
return 0;
}
void Prim()
{
res = 0;
//设置1为起始点,但是并没有放入1
mincost[1] = 0;
//used[1] = true;
int times = 0;
//没必要放入1
// for( int i = 1; i <= N; i++ )
// mincost[i] = cost[1][i];
while( true ){
int v = -1;
for( int i = 1; i <= N; i++ ){
if( !used[i] && (v == -1 || mincost[i] < mincost[v] ) )
v = i;
}
if( v == -1 || mincost[v] == INF )
break;
used[v] = true;
res += mincost[v];
times++;
for( int i = 1; i <= N; i++ ){
mincost[i] = min(mincost[i],cost[v][i]);
}
}
//相当于加了N个点
if( times != N )
res = -1;
return ;
}
Kruskal:
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#define MAX_N 510
#define MAX_M 25010
#define MAX_K 110
#define MAX_C 1010
using namespace std;
struct node{
int from,to;
int cost;
};
int Left[MAX_K];
node edges[MAX_M];
int Father[MAX_N];
int Rank[MAX_N];
int times;
int res;
int N,M,K;
int p,q,c;
int num;
void Ini();
void kruskal();
int Find(int x);
bool Same(int x,int y);
bool Unite(int x,int y);
bool cmp( const node &e1,const node &e2){
return e1.cost < e2.cost;
}
int main()
{
scanf("%d",×);
while( times-- )
{
scanf("%d%d%d",&N,&M,&K);
Ini();
for( int i = 0; i < M; i++ )
{
scanf("%d%d%d",&p,&q,&c);
edges[i].from = p;
edges[i].to = q;
edges[i].cost = c;
}
// memset(Left,0,sizeof(Left));
for( int i = 0; i < K; i++ )
{
int KK;
scanf("%d",&KK);
for( int j = 0; j < KK; j++ )
{
scanf("%d",&Left[j]);
//将后面的节点全部连到第一个点上
if( Unite(Left[j],Left[0]) )
num++;
}
}
kruskal();
if( num != N-1 )
res = -1;
printf("%d\n",res);
}
return 0;
}
void Ini()
{
memset(Left,0,sizeof(Left));
res = 0;
num = 0;
for( int i = 1; i <= N; i++ )
{
Father[i] = i;
Rank[i] = 1;
}
return ;
}
void kruskal()
{
sort(edges,edges+M,cmp);
for( int i = 0; i < M; i++ )
{
node temp = edges[i];
if( Unite(temp.from,temp.to) )
{
res+=temp.cost;
num++;
}
//最小生成树只有N-1条边
if( num == N-1 )
break;
}
return ;
}
//在这里same函数没太大用处,使用的话甚至还是一种时间上的浪费
bool Same(int x,int y)
{
return Find(x) == Find(y);
}
bool Unite(int x,int y)
{
x = Find(x);
y = Find(y);
if( x == y )
return false;
if( Rank[x] < Rank[y] )
Father[x] = y;
else
{
Father[y] = x;
if( Rank[x] == Rank[y] )
Rank[x]++;
}
return true;
}
int Find(int x)
{
int r,k,j;
r = x;
while( r != Father[r] )
r = Father[r];
k = x;
while( Father[k] != r )
{
j = Father[k];
Father[k] = r;
k = j;
}
return r;
}