题目描述
有N个村庄,编号从1到N,你应该建造一些道路,使每个村庄都可以相互连接。
两个村A和B是相连的,当且仅当A和B之间有一条道路,或者存在一个村C使得在A和C之间有一条道路,并且C和B相连。
现在一些村庄之间已经有一些道路,你的任务就是修建一些道路,使所有村庄都连通起来,并且所有道路的长度总和是最小的。
输入
测试数据有多组
第一行是整数N(3 <= N <= 100),代表村庄的数量。 然后是N行,其中第i行包含N个整数,这些N个整数中的第j个是村庄i和村庄j之间的距离(距离是[1,1000]内的整数)。
然后是整数Q(0 <= Q <= N *(N + 1)/ 2),接下来是Q行,每行包含两个整数a和b(1 <= a <b <= N),代表着村庄a和村庄b之间的道路已经建成。
输出
对于每组测试数据
输出一个整数,表示要构建的道路的长度总和最小值
输入样例1
3
0 990 692
990 0 179
692 179 0
1
1 2
输出样例1
179
NOTICE:
关于这题有些疑惑:题目里专门拎一段出来说两个村庄之间最多只能相隔一个村庄,结果老师告诉我不用考虑。。。。这题读完题之后就大概能知道要用最小生成树的算法,但是呢,它给出的村庄的已建设的道路可能有闭环,这就已经不是最小生成树了吧?
正题:这题用的是克鲁斯卡尔算法(Kruskal),在原有算法的基础上做一些小改动:读入的是邻接矩阵,要将其修改为edge数组,本题为无向图,因此只需要遍历邻接矩阵的一半;已建设的道路,将该道路两端的顶点修改为同一flag,表示两顶点已连接;选边次数未知,因此需要遍历所有的边,不用担心会多选,因为当所有的顶点的flag一致时不会再选择新的边;本题还需要注意题目中“有多组测试数据”的要求,实现如主函数代码所示。
#include <iostream>
#include <algorithm>
using namespace std;
class Edge
{
public:
int start, end;
int weight;
};
bool cmp(Edge e1, Edge e2)
{
if (e1.weight < e2.weight)
return true;
return false;
}
class Graph
{
private:
int** Matrix;
Edge* edge;
int vertexnum;
int edgenum;
int finishnum;//已有道路数量
int* flag;//顶点标号,判断是否构成闭环
public:
Graph(int num)
{
vertexnum = num;
Matrix = new int* [vertexnum];
for (int i = 0; i < vertexnum; i++)
Matrix[i] = new int[vertexnum];
for (int i = 0; i < vertexnum; i++)
for (int j = 0; j < vertexnum; j++)
cin >> Matrix[i][j];
//edge数组创建
//n个顶点,每两个之间都有一条边,那么就有n(n-1)/2条边(排列组合)
edgenum = vertexnum * (vertexnum - 1) / 2;
edge = new Edge[edgenum];
int index = 0;//edge的下标
//将邻接矩阵转换为edge数组
//因为是无向网,因此只需遍历一半,同时主对角线上的也不需要遍历
for(int i=0;i<vertexnum;i++)
for (int j = i + 1; j < vertexnum; j++)
{
edge[index].start = i;
edge[index].end = j;
edge[index].weight = Matrix[i][j];
index++;
}
//flag初始化为自己的下标
flag = new int[vertexnum];
for (int i = 0; i < vertexnum; i++)
flag[i] = i;
//已有的道路,修改flag,注意村庄下标从1开始
cin >> finishnum;
for (int i = 0; i < finishnum; i++)
{
int a, b;
cin >> a >> b;
int tmp = flag[b - 1];
for (int j = 0; j < vertexnum; j++)
if (flag[j] == tmp)
flag[j] = flag[a - 1];
}
}
~Graph()
{
for (int i = 0; i < vertexnum; i++)
delete[]Matrix[i];
delete[]Matrix;
delete[]edge;
delete[]flag;
}
void Kruskal()
{
int weight_sum = 0;
//升序排序
sort(edge, edge + edgenum, cmp);
//这里并不知道要修多少条道路,因此遍历所有的道路,当所有的道路的flag都一致时,多余的边自然都选不了
for (int i = 0; i < edgenum; i++)
{
if (flag[edge[i].start] != flag[edge[i].end])//这条边的start的flag 不等于 这条边的end的flag,也就是不构成闭环
{
weight_sum += edge[i].weight;
//把这条边的两个顶点的flag改为一致(哪个赋给哪个都可以)
//有时候是两个分量的连接,这时候改的就不止一个顶点,是其中一个分量的所有顶点,都改为与另一个分量一致
//这里统一将end改为与start一致
int tmp = flag[edge[i].end];//一定要用一个临时变量记录,因为原来的end的flag会被改为与start一致
for (int j = 0; j < vertexnum; j++)
{
if (flag[j] == tmp)
flag[j] = flag[edge[i].start];
}
}
}
//输出
cout << weight_sum << endl;
}
};
int main()
{
int num;
while (cin >> num)
{
Graph g(num);
g.Kruskal();
}
return 0;
}