Data Structures and Algorithms7-19——Battle Over Cities - Hard Version

我的Data Structures and Algorithms代码仓:https://github.com/617076674/Data-Structures-and-Algorithms

原题链接:https://pintia.cn/problem-sets/16/problems/681

题目描述:

知识点:最小生成树问题、prim算法、kruskal算法

思路一:prim算法

本题的本质是最小生成树问题,对除去第i个顶点(i = 1, 2, ... , N)后的N - 1个顶点求其最小生成树。对于Status == 1的路径,其边权为0,对Status == 0的路径,其边权就是Cost

prim算法的具体实现:

prim算法需要实现两个关键的概念,即集合S的实现、顶点Vi(1 <= i <= N)与集合S的最短距离。

(1)集合S的实现方法和Dijkstra中相同,即使用一个bool型数组visited[]表示顶点是否已被访问。其中visited[i] == true表示顶点Vi已被访问,visited[i] == false表示顶点Vi未被访问。

(2)令int型数组d[]来存放顶点Vi(1 <= i <= N)与集合S的最短距离。初始时除了起点s的d[s]赋为0,其余顶点都赋为一个很大的数来表示INF,即不可达。

注意:如果去掉某个顶点后剩余的城市不连通,需要输出一个很大的数来表示INF,这样表示该城市一旦失陷,付出再大的代价也无法使得剩余图保持连通,测试点3就是这样一组测试用例

时间复杂度是O(N ^ 2)。空间复杂度是O(N + M)。

C++代码:

#include<iostream>
#include<vector>

using namespace std;

struct node {
	int v, cost;
	node(int _v, int _cost) {
		v = _v;
		cost = _cost;
	}
};

int N, M, City1, City2, Cost, Status, lost, INF = 1000000000;
vector<node> graph[501];
int costs[501], d[501];
bool visited[501];

int prim();

int main() {
	scanf("%d %d", &N, &M);
	fill(costs + 1, costs + N + 1, 0);
	for(int i = 0; i < M; i++) {
		scanf("%d %d %d %d", &City1, &City2, &Cost, &Status);
		if(Status == 1) {	//如果这条高速公路未损坏,则其费用为0
			Cost = 0;
		}
		graph[City1].push_back(node(City2, Cost));
		graph[City2].push_back(node(City1, Cost));
	}
	for(int i = 1; i <= N; i++) {
		lost = i;
		costs[i] = prim();
	}
	int maxIndex = 1;	//求出最大花费的下标
	for(int i = 2; i <= N; i++) {
		if(costs[i] > costs[maxIndex]) {
			maxIndex = i;
		}
	}
	if(costs[maxIndex] == 0) {	//如果最大花费为0,直接输出0并return 0
		printf("0\n");
		return 0;
	}
	vector<int> maxCosts;	//存储最大花费的丢失的城市的索引
	for(int i = 1; i <= N; i++) {
		if(costs[i] == costs[maxIndex]) {
			maxCosts.push_back(i);
		}
	}
	for(int i = 0; i < maxCosts.size(); i++) {	//输出结果
		printf("%d", maxCosts[i]);
		if(i != maxCosts.size() - 1) {
			printf(" ");
		} else {
			printf("\n");
		}
	}
	return 0;
}

int prim() {
	fill(visited + 1, visited + 501, false);	//所有点都未被访问过
	fill(d + 1, d + 501, INF);
	if(lost == 1) {
		d[2] = 0;
	} else {
		d[1] = 0;
	}
	int ans = 0;
	for(int i = 0; i < N - 1; i++) {
		int u = -1, min = INF;
		for(int j = 1; j <= N; j++) {
			if(!visited[j] && d[j] < min && j != lost) {
				u = j;
				min = d[j];
			}
		}
		if(u == -1) {
			return INF;	//如果找不到小于INF的d[u],则剩下的顶点和集合S不连通,此时的花费是最高的 
		}
		visited[u] = true;
		ans += d[u];
		for(int j = 0; j < graph[u].size(); j++) {
			int v = graph[u][j].v;
			int len = graph[u][j].cost;
			if(!visited[v] && v != lost && len < d[v]) {
				d[v] = len;
			}
		}
	}
	return ans;
}

C++解题报告:

思路二:kruskal算法(测试点4和5会超时)

kruskal算法的基本思想为:在初始状态隐去图中的所有边,这样图中每个顶点都自成一个连通块。之后执行下面的步骤:

(1)对所有边按边权从小到大进行排序。

(2)按边权从小到大测试所有边,如果当前测试边所连接的两个顶点不在一个连通块中,则把这条测试边加入当前最小生成树中;否则,将边舍弃。

(3)执行步骤(2),直到最小生成树中的边数等于总顶点数减1或是测试完所有边时结束。而当结束时如果最小生成树的边数小于总顶点数减1,说明该图不连通,此时需要返回一个表示无穷大的数INF

时间复杂度是O(MlogM)。空间复杂度是O(M)。

C++代码:

#include<iostream>
#include<vector>
#include<algorithm>

using namespace std;

struct edge {
	int u, v, cost;
	edge(int _u, int _v, int _cost) {
		u = _u;
		v = _v;
		cost = _cost;
	}
};

int N, M, City1, City2, Cost, Status, lost, INF = 1000000000;
vector<edge> edges;
int costs[501], father[501];

int kruskal();
int findFather(int x);
bool cmp(edge e1, edge e2);

int main() {
	scanf("%d %d", &N, &M);
	fill(costs + 1, costs + N + 1, 0);
	for(int i = 0; i < M; i++) {
		scanf("%d %d %d %d", &City1, &City2, &Cost, &Status);
		if(Status == 1) {	//如果这条高速公路未损坏,则其费用为0
			Cost = 0;
		}
		edges.push_back(edge(City1, City2, Cost));
	}
	for(int i = 1; i <= N; i++) {
		lost = i;
		costs[i] = kruskal();
	}
	int maxIndex = 1;	//求出最大花费的下标
	for(int i = 2; i <= N; i++) {
		if(costs[i] > costs[maxIndex]) {
			maxIndex = i;
		}
	}
	if(costs[maxIndex] == 0) {	//如果最大花费为0,直接输出0并return 0
		printf("0\n");
		return 0;
	}
	vector<int> maxCosts;	//存储最大花费的丢失的城市的索引
	for(int i = 1; i <= N; i++) {
		if(costs[i] == costs[maxIndex]) {
			maxCosts.push_back(i);
		}
	}
	for(int i = 0; i < maxCosts.size(); i++) {
		printf("%d", maxCosts[i]);
		if(i != maxCosts.size() - 1) {
			printf(" ");
		} else {
			printf("\n");
		}
	}
	return 0;
}

int kruskal() {
	int ans = 0, num_edge = 0;
	for(int i = 1; i <= N; i++) {	//并查集的初始化
		father[i] = i;
	}
	sort(edges.begin(), edges.end(), cmp);
	for(int i = 0; i < edges.size(); i++){
		int u = edges[i].u;
		int v = edges[i].v;
		if(u == lost || v == lost){
			continue;
		}
		int faU = findFather(u);
		int faV = findFather(v);
		if(faU != faV){
			father[faU] = faV;
			ans += edges[i].cost;
			num_edge++;
			if(num_edge == N - 2){
				break;
			}
		}
	}
	if(num_edge != N - 2){
		return INF;
	}
	return ans;
}

int findFather(int x) {
	int a = x;
	while(x != father[x]) {
		x = father[x];
	}
	while(a != father[a]) {
		int z = a;
		a = father[a];
		father[z] = x;
	}
	return x;
}

bool cmp(edge e1, edge e2){
	return e1.cost < e2.cost;
}

C++解题报告:

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值