一本通 3.4.5 最小生成树

1348:【例4-9】城市公交网建设问题

【题目描述】

有一张城市地图,图中的顶点为城市,无向边代表两个城市间的连通关系,边上的权为在这两个城市之间修建高速公路的造价,研究后发现,这个地图有一个特点,即任一对城市都是连通的。现在的问题是,要修建若干高速公路把所有城市联系起来,问如何设计可使得工程的总造价最少?

【题目分析】

最小生成树模板题,Kruskal算法和Prim算法均可以

【代码实现】

Prim算法

#include<bits/stdc++.h>
using namespace std;

struct node {
	int from;
	int to;
	int dis;
	friend bool operator<(node a, node b) {
		return a.dis >= b.dis;
	}
};
vector<node> edge[105];
priority_queue<node> que;
bool vis[105];
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int x, y, w;
		cin >> x >> y >> w;
		edge[x].push_back({x, y, w});
		edge[y].push_back({y, x, w});
	}
	//init priority_queue
	vis[1] = 1;
	for (auto e : edge[1]) {
		que.push(e);
	}
	//loop resolve
	while (!que.empty()) {
		node a = que.top();
		que.pop();
		if (!vis[a.to]) {
			vis[a.to] = 1;
			cout << a.from << " " << a.to << endl;
			for (auto e : edge[a.to]){
				que.push(e);
			} 
		}
	}
	return 0;
}

Kruskal算法

#include<bits/stdc++.h>
using namespace std;

struct node {
	int from, to, dis;
};
bool cmp(node &a, node &b) {
	return a.dis < b.dis;
}
vector<node> edge;
int fa[105];

int find(int a) {
	return fa[a] == a ? a : fa[a] = find(fa[a]);
}
int main() {
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		fa[i] = i;
	}
	for (int i = 1; i <= m; i++) {
		int x, y, w;
		cin >> x >> y >> w;
		edge.push_back({x, y, w});
		edge.push_back({y, x, w});
	}

	sort(edge.begin(), edge.end(), cmp);

	for (auto e : edge) {
		int from = find(e.from);
		int to = find(e.to);
		if (from != to) {
			cout << e.from << " " << e.to << endl;
			fa[from] = to ;
		}
	}

	return 0;
}

1349:【例4-10】最优布线问题

【题目描述】

学校有n台计算机,为了方便数据传输,现要将它们用数据线连接起来。两台计算机被连接是指它们有数据线连接。由于计算机所处的位置不同,因此不同的两台计算机的连接费用往往是不同的。

当然,如果将任意两台计算机都用数据线连接,费用将是相当庞大的。为了节省费用,我们采用数据的间接传输手段,即一台计算机可以间接的通过若干台计算机(作为中转)来实现与另一台计算机的连接。

现在由你负责连接这些计算机,任务是使任意两台计算机都连通(不管是直接的或间接的)。

【题目分析】

最小生成树模板题,Kruskal算法和Prim算法均可

【代码实现】

Prim算法

#include <bits/stdc++.h>
#define MAXN 101
using namespace std;

const int inf = 0x3f3f3f3f;

bool vis[MAXN];
int dis[MAXN], e[MAXN][MAXN];
int ans = 0;
int n, m;

int main() {
	//input data
	//clock_t s = clock();
	//init
	cin >> n;

	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&e[i][j]);	
		}
	}

	for (int i = 0; i <= n; i++) {
		dis[i]=inf;
	}
	dis[1] = 0;
	for (int i = 1; i <= n; i++) {
		int k = 0;
		for (int j = 1; j <= n; j++) { //寻找与当前构造完的生成树最近的点
			if (vis[j]) continue;
			if (dis[j] < dis[k]) k = j;
		}

		ans += dis[k];
		vis[k] = true;
		for (int j = 1; j <= n; j++) { //更新其他点到生成树的距离
			if (e[k][j] < dis[j]) {
				dis[j] = e[k][j];
			}
		}
	}
	cout<<ans;
	//output time
	//cout <<endl<< clock() - s<<endl;
	return 0;
}

1350:【例4-11】最短网络(agrinet)

【题目描述】

农民约翰被选为他们镇的镇长!他其中一个竞选承诺就是在镇上建立起互联网,并连接到所有的农场。当然,他需要你的帮助。约翰已经给他的农场安排了一条高速的网络线路,他想把这条线路共享给其他农场。为了用最小的消费,他想铺设最短的光纤去连接所有的农场。你将得到一份各农场之间连接费用的列表,你必须找出能连接所有农场并所用光纤最短的方案。每两个农场间的距离不会超过100000100000。

【题目分析】

最小生成树模板题,Kruskal算法和Prim算法均可

【代码实现】

Prim算法

#include <bits/stdc++.h>
#define MAXN 101
using namespace std;

const int inf = 0x3f3f3f3f;

bool vis[MAXN];
int dis[MAXN], e[MAXN][MAXN];
int ans = 0;
int n, m;

int main() {
	cin >> n;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&e[i][j]);	
		}
	}

	for (int i = 0; i <= n; i++) {
		dis[i]=inf;
	}
	
	dis[1] = 0;
	for (int i = 1; i <= n; i++) {
		int k = 0;
		for (int j = 1; j <= n; j++) { //寻找与当前构造完的生成树最近的点
			if (vis[j]) continue;
			if (dis[j] < dis[k]) k = j;
		}

		ans += dis[k];
		vis[k] = true;
		for (int j = 1; j <= n; j++) { //更新其他点到生成树的距离
			if (e[k][j] < dis[j]) {
				dis[j] = e[k][j];
			}
		}
	}
	cout<<ans;
	return 0;
}

1351:【例4-12】家谱树

【题目描述】

有个人的家族很大,辈分关系很混乱,请你帮整理一下这种关系。

给出每个人的孩子的信息。

输出一个序列,使得每个人的后辈都比那个人后列出。

【题目分析】

拓扑排序模板题
方法1:移除入度为0的点,并将相邻点的入度减1,依次循环直到所有点都被输出
方法2:对所有的点进行深搜,在每个点返回时加入栈,搜索完成后依次弹出点

【代码实现】

方法1:

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1001;
int n;
vector<int> p[maxn];
int indeg[maxn];
int vis[maxn];
queue<int> Q;
int main() {
	cin >> n ;
	for (int i = 1; i <= n; i++) {
		int x;
		while (cin >> x, x != 0) {
			p[i].push_back(x);
			indeg[x]++;
		}
	}
	for (int i = 1; i <= n; i++) { //找到起点
		if (indeg[i] == 0) Q.push(i);
	}
	while (!Q.empty()) {
		int u = Q.front();
		Q.pop();
		cout << u << " ";
		for (int i = 0; i < (int ) p[u].size(); i++) { //依次找后续节点
			if (--indeg[p[u][i]] == 0)
				Q.push(p[u][i]);
		}
	}
	return 0;
}

方法2: 

#include <bits/stdc++.h>

using namespace std;

vector<int> p[105];
stack<int> sta;
bool vis[105];
void dfs(int x) {

	for (auto e : p[x]) {
		if (vis[e]) continue;
		vis[e] = 1;
		dfs(e);
	}
	sta.push(x);
}
int main() {
	//input data
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int x;
		while (cin >> x, x != 0) p[i].push_back(x);
	}

	for (int i = 1; i <= n; i++) {
		if (!vis[i]) vis[i] = 1, dfs(i);
	}

	while (!sta.empty())  cout << sta.top() << " ", sta.pop();
	return 0;
}

1391:局域网(net)

【题目描述】

某个局域网内有n(n≤100)台计算机,由于搭建局域网时工作人员的疏忽,现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象。因为连接计算机的网线本身不同,所以有一些连线不是很畅通,我们用f(i,j)表示i,j之间连接的畅通程度(f(i,j)≤1000),f(i,j)值越小表示i,j之间连接越通畅,f(i,j)为00表示i,j之间无网线连接。现在我们需要解决回路问题,我们将除去一些连线,使得网络中没有回路,并且被除去网线的Σf(i,j)最大,请求出这个最大值。

【题目分析】

使得网络中没有回路,并且被除去网线的Σf(i,j)最大,即留下的网线可以生成一个最小生成树,将所有网线的权值相加减去最小生成树的权值,得到最大的移除权值。

算法实现:Kruskal算法或者Prim算法求解最小生成树的权值和

【代码实现】

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1001;
struct Edges {
	int u;
	int v;
	int w;
} edge[maxn * 50];
inline bool cmp(const Edges &a, const Edges &b) {
	return a.w < b.w;
}

int n, m;
int father[maxn];
int find(int x) {
	if (father[x] != x)
		father[x] = find(father[x]);
	return father[x];
}
int ans = 0;
int main() {
	//input data
	//clock_t s = clock();
	int sum = 0;
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		cin >> edge[i].u >> edge[i].v >> edge[i].w;
		sum += edge[i].w;
	}

	sort(edge + 1, edge + m + 1, cmp);
	for (int i = 1; i <= n; i++) {
		father[i] = i;
	}
	int _count = 0;
	for (int i = 1; i <= m; i++) {
		int fa_u = find(edge[i].u);
		int fa_v = find(edge[i].v);
		if (fa_u != fa_v) {
			ans += edge[i].w;
			father[fa_u] = fa_v;
			_count++;
			if (_count >= n - 1) break;
		}

	}
	cout << sum - ans;
	//output time
	//cout <<endl<< clock() - s<<endl;
	return 0;
}

1392:繁忙的都市(city)

【题目描述】

城市C是一个非常繁忙的大都市,城市中的道路十分的拥挤,于是市长决定对其中的道路进行改造。城市C的道路是这样分布的:城市中有n个交叉路口,有些交叉路口之间有道路相连,两个交叉路口之间最多有一条道路相连接。这些道路是双向的,且把所有的交叉路口直接或间接的连接起来了。每条道路都有一个分值,分值越小表示这个道路越繁忙,越需要进行改造。但是市政府的资金有限,市长希望进行改造的道路越少越好,于是他提出下面的要求:

1.改造的那些道路能够把所有的交叉路口直接或间接的连通起来。

2.在满足要求1的情况下,改造的道路尽量少。

3.在满足要求1、2的情况下,改造的那些道路中分值最大值尽量小。

作为市规划局的你,应当作出最佳的决策,选择那些道路应当被修建。

【题目分析】

根据题意:将所有交叉路口直接或间接的连接起来,并使得所有路口的分值之和最小,就是求连接所有交叉路口的最小生成树。

算法实现:Kruskal算法或者Prim算法求解最小生成树的权值和

【代码实现】

#include <bits/stdc++.h>

using namespace std;
const int maxn = 1001;
struct Edges {
	int u;
	int v;
	int w;
} edge[maxn * 50];
inline bool cmp(const Edges &a, const Edges &b) {
	return a.w < b.w;
}

int n, m;
int father[maxn];
int find(int x) {
	if (father[x] != x)
		father[x] = find(father[x]);
	return father[x];
}
int ans = 0;
int main() {
	//input data
	//clock_t s = clock();
	int sum = 0;
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		cin >> edge[i].u >> edge[i].v >> edge[i].w;
		sum += edge[i].w;
	}

	sort(edge + 1, edge + m + 1, cmp);
	for (int i = 1; i <= n; i++) {
		father[i] = i;
	}
	int maxw = 0;
	int _count = 0;
	for (int i = 1; i <= m; i++) {
		int fa_u = find(edge[i].u);
		int fa_v = find(edge[i].v);
		if (fa_u != fa_v) {
			maxw = max(maxw, edge[i].w);
			father[fa_u] = fa_v;
			_count++;
			if (_count >= n - 1) break;
		}

	}
	cout << n - 1 << " " << maxw;
	return 0;
}

1393:联络员(liaison)

【题目描述】

Tyvj已经一岁了,网站也由最初的几个用户增加到了上万个用户,随着Tyvj网站的逐步壮大,管理员的数目也越来越多,现在你身为Tyvj管理层的联络员,希望你找到一些通信渠道,使得管理员两两都可以联络(直接或者是间接都可以)。Tyvj是一个公益性的网站,没有过多的利润,所以你要尽可能的使费用少才可以。

目前你已经知道,Tyvj的通信渠道分为两大类,一类是必选通信渠道,无论价格多少,你都需要把所有的都选择上;还有一类是选择性的通信渠道,你可以从中挑选一些作为最终管理员联络的通信渠道。数据保证给出的通行渠道可以让所有的管理员联通。

【题目分析】

必选的通信渠道相加,将连接点进行并查集合并,对可选的通讯渠道排序,依次选择可选渠道使得所有点连通,Kruskal算法思想

【代码实现】

#include <bits/stdc++.h>

using namespace std;
const int maxn = 2005;
struct Edges {
	int u;
	int v;
	int w;
} edge[maxn * 50];
inline bool cmp(const Edges &a, const Edges &b) {
	return a.w < b.w;
}

int n, m;
int father[maxn];
int find(int x) {
	if (father[x] != x)
		father[x] = find(father[x]);
	return father[x];
}
int ans = 0;
int main() {
	//input data
	cin >> n >> m;
	int index = 0;
	int _count = 0;
	for (int i = 1; i <= n; i++) {  //init bing cha ji
		father[i] = i;
	}
	for (int i = 1; i <= m; i++) {
		int x, y, q, w;
		cin >> q >> x >> y >> w;
		if (q == 1) {          //direct solve
			ans += w;
			int fa_x = find(x);
			int fa_y = find(y);
			if (fa_x != fa_y) {
				father[fa_y] = fa_x;
				_count++;
			}
		}
		if (q == 2) {          //save data
			edge[++index].u = x;
			edge[index].v = y;
			edge[index].w = w;
		}

	}
	//sort and add edges
	sort(edge + 1, edge + index + 1, cmp);
	for (int i = 1; i <= index; i++) {
//		cout << edge[i].w << " ";
		int fa_u = find(edge[i].u);
		int fa_v = find(edge[i].v);
		if (fa_u != fa_v) {
			ans += edge[i].w;
			father[fa_v] = fa_u;
			_count++;
			if (_count >= n - 1) break;
		}

	}
	//cout answers
	cout << ans;
	return 0;
}

1394:连接格点(grid)

【题目描述】

有一个M行N列的点阵,相邻两点可以相连。一条纵向的连线花费一个单位,一条横向的连线花费两个单位。某些点之间已经有连线了,试问至少还需要花费多少个单位才能使所有的点全部连通。

【题目分析】

根据已经相连的线,将线的两端点进行并查集合并,枚举竖边将端点并查集合并,费用加1,枚举横边将端点并查集合并,费用加2,输出结果  Kruskal算法思想

【代码实现】

 

#include <bits/stdc++.h>

using namespace std;

struct node {
	int x, y;
};
node fa[1005][1005];

node find(node a) {
	if (a.x == fa[a.x][a.y].x && a.y == fa[a.x][a.y].y) return a;
	return fa[a.x][a.y] = find(fa[a.x][a.y]);
}
bool merge(node a, node b) {
	node fa_a = find(a);
	node fa_b = find(b);
	if (fa_a.x != fa_b.x || fa_a.y != fa_b.y) {
		fa[fa_a.x][fa_a.y] = fa_b;
		return true;
	}
	return false;
}
int main() {
	//input data
	int m, n;
	cin >> m >> n;
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) {
			fa[i][j] = {i, j};
		}
	}

	int sx, sy, fx, fy;
	while (cin >> sx >> sy >> fx >> fy) {
		merge({sx, sy}, {fx, fy});
	}

	int sum = 0;
	//先枚举竖边
	for (int i = 1; i < m; i++) {
		for (int j = 1; j <= n; j++) {
			if (merge({i, j}, {i + 1, j})) sum++;
		}
	}
	//再枚举横边
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j < n; j++) {
			if (merge({i, j}, {i, j + 1})) sum += 2;
		}
	}
	cout << sum << endl;
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值