DS道路建设 (Ver. I)

题目描述

有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;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值