P2330 [SCOI2005]繁忙的都市 题解

本题来源于Luogu,传送门


前言

本题难度:普及/提高-

涉及算法:图论、并查集、生成树

涉及语法:sort()函数(结构体)比较器

        本题算是作为自己做的第一个最小生成树相关的题目,特对本题自己了解掌握学习到的相关语法算法进行汇总整理,由于基本的并查集模块比较熟练,本博客将主要聚焦于其他的部分。


一、并查集

主要思想:用一个代表元素作为一个集合的特征,对该元素修改实现合并集合的操作merge(),对该元素的访问比较实现查询两个对象是否在同一集合getFather()。

具体并查集的知识不在此赘述。感兴趣的读者可以查阅相关CSDN博客。

二、图论——生成树

在CSDN发现了一篇优质的图论——生成树合集,作者Alex_McAvoy,传送门

对一个具有 n 个点的连通图进行遍历,对于遍历后的子图,其包含原图中所有的点且保持图连通,最后的结构一定是一个具有 n-1 条边的树,通常称为生成树

        在生成树问题中,最常见的问题就是最小生成树问题,所谓最小生成树,就是对于一个有 n 个点的无向连通图的生成树,其包含原图中的所有点,且保持图连通的边权总和最少的边。

        简单来说,对于一个有 n 个点的图,边一定是大于等于 n-1 条的,最小生成树,就是在这些边中选择 n-1 条出来连接所有的 n 个点,且这 n-1 条边的边权之和是所有方案中最小的。

        最小生成树具有以下两条性质:

                切割性质:连接点 x、y 的边权最小的边必定被生成树包含
                回路性质:任意回路/环上的边权最大的边必不被生成树包含
        求最小生成树一般有 Prim 算法Kruskal 算法,其中,Prim 算法时间复杂度为 O(V*V),与图中边数无关,适合稠密图;Kruskal 算法时间复杂度 为O(ElogE),需要对图的边进行访问,适合稀疏图。

————————————————
版权声明:本文为CSDN博主「Alex_McAvoy」的原创文章,遵循CC 4.0 BY-SA版权协议,
原文链接:https://blog.csdn.net/u011815404/article/details/88625346

对于最小生成树 ,一般有Prim算法与Kruskal算法,在浅浅看了两个算法之后,惊奇地发现Kruskal算法如此清晰易懂——排序(贪心)+并查集即可解决,上手无难度。

算法大意:

        Kruskal 算法基本思想是并查集思想,将所有边升序排序,并认为每一个点都是孤立的,分属 n 个独立的集合。

        按顺序枚举每一条边,如果这条边连接的两个点分属两个不同的集合,那么就将这条边加入最小生成树,这两个不同的集合合并为一个集合;如果这条边连接的两个点属于同一集合,那么就跳过。直到选取 n-1条边为止(只剩一个集合)。

        其时间复杂度为:O(E*logE),E代表边数。

三、sort()比较器

sort()函数存放在<algorithm>头文件中,关于sort()的用法,大致有以下两种

//对 [first, last) 区域内的元素做默认的升序排序
void sort (RandomAccessIterator first, RandomAccessIterator last);
//按照指定的 comp 排序规则,对 [first, last) 区域内的元素进行排序
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);

自定义排序,我们需要第二种,并且通过自定义比较函数comp来实现排序的自定义,而我们只关心怎样才返回逻辑上的升序降序。

comp函数的定义需要两个参数,参数a和参数b,当满足一定的条件的时候返回ture或false,只需要记住一条规则:

1.当返回值为true时,第一个参数放在前面,第二个参数放在后面; false则反之
2.当a = b时,必须要返回fasle

        记住这两条规则,就可以自定义排序函数实现自定义排序的目的。

四、P2330 [SCOI2005]繁忙的都市

题目描述

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

  1. 改造的那些道路能够把所有的交叉路口直接或间接的连通起来。
  2. 在满足要求 1 的情况下,改造的道路尽量少。
  3. 在满足要求 1、2 的情况下,改造的那些道路中分值最大的道路分值尽量小。

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

输入格式

第一行有两个整数 n,m

        n,m 表示城市有 nn 个交叉路口,mm 条道路。

接下来 m 行是对每条道路的描述,u, v, c

        u,v,c 表示交叉路口 uu 和 vv 之间有道路相连,分值为 cc。

输出格式

两个整数 s, max,表示你选出了几条道路,分值最大的那条道路的分值是多少。

说明/提示

数据范围及约定

对于全部数据,满足 1\le n\le 3001≤n≤300,1\le c\le 10^41≤c≤104,1 \le m \le 10^51≤m≤105。

输入样例#1:

4 5
1 2 3
1 4 5
2 4 7
2 3 6
3 4 8

输出样例#1:

3 6

 五、题目分析

· 将所有的路口联通——连通块——并查集问题。

· 道路具有分值——边权——图论。

· “分值最大的道路分值尽量小”+图论——最小生成树。

本题算是一道最小生成树的裸题,思路十分明确:

1、以分值为关键字升序排序  2、并查集筛选

由于每一条路涉及到路两端的十字路口、 路的“分值”,所以我选择采用结构体的方式存数据。 

struct crossroads{
	int a,b;
	int value;
}roads_data[100005];

 那么对结构体数组的排序,如若图方便不自写排序,用头文件<algorithm>的sort()函数,就需要编写相应的比较函数。根据相应的分值升序排列。

bool cmpMod(crossroads a,crossroads b)
{
	return a.value<b.value? true:false;
    //return a.value<b.value;
}

 至于排完序,这就是一道裸裸的并查集题目,并没有其他的陷阱。

int road[305];

int getFather(int x)
{
	if(road[x]==x)return x;
	return road[x]=getFather(road[x]);
}

void merge(int x,int y)
{
	road[getFather(x)]=getFather(y);
}

六、过关代码

至此,所有的源代码如下。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iomanip>
#include<limits.h>

using namespace std;

struct crossroads{
	int a,b;
	int value;
}roads_data[100005];

bool cmpMod(crossroads a,crossroads b)
{
	return a.value<b.value? true:false;
    //return a.value<b.value;
}

int n,m;

int road[305];

int getFather(int x)
{
	if(road[x]==x)return x;
	return road[x]=getFather(road[x]);
}

void merge(int x,int y)
{
	road[getFather(x)]=getFather(y);
}

int times=0;
int maxx;
int main()
{
	cin>>n>>m;
	for(int i=0;i<m;++i)
	{
		cin>>roads_data[i].a>>roads_data[i].b>>roads_data[i].value;
	}
	sort(roads_data,roads_data+m,cmpMod);
	for(int i=0;i<n;++i)
	{
		road[i]=i;
	}
	for(int i=0;i<m;++i)
	{
		if(getFather(roads_data[i].a) != getFather(roads_data[i].b))
		{
			merge(roads_data[i].a,roads_data[i].b);
			times++;
			maxx=roads_data[i].value;
		}
	}
	cout<<times<<" "<<maxx;
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值