信息学奥赛一本通 1487:【例 2】北极通讯网络

【题目链接】

ybt 1487:【例 2】北极通讯网络

【题目考点】

1. 二分答案
2. 最小生成树

【解题思路】

解法1:二分答案

每个村庄是一个顶点,直角坐标系中两点之间的距离为两顶点之间的边权。

如果已经确定了无线电收发机的通信距离 d d d。

两村庄之间的距离如果小于等于 d d d,可以使用无线电通讯,认为两顶点之间有一条边。

这样整个图会形成多个连通分量,每个连通分量内的顶点互相连通,也就是这些村庄可以通过无线电直接通讯。

如果只有1个连通分量,可以不使用卫星设备。

如果连通分量的数量大于1,每个连通分量必须分配一个卫星设备,保证各连通分量中的村庄都可以相互通信。由于卫星设备只有 k k k个,连通分量的数量必须小于等于 k k k。

如果输入的 k = 0 k=0 k=0,则没有卫星设备,这时只有图中只存在1个连通分量的情况是满足条件的。这与 k = 1 k=1 k=1时,图中连通分量数量要小于等于 1 1 1的情况是相同的。

因此为了处理方便,如果 k = 0 k=0 k=0,则人为将 k k k设为 1 1 1 ,接下来同意使用"连通分量的数量是否小于等于 k k k"来进行判断。

无线电通信距离 d d d越大,可能有更多的村庄之间可以使用无线电通讯,图中可以连的边越多,连通分量越少。无线电通信距离 d d d越小,图中边越少,连通分量越多。

连通分量的数量与 d d d满足单调性,可以使用二分答案求 d d d的值。

  • 答案变量:无线电通信距离 d d d
  • 最值:最小值
  • 满足条件:无线电通信距离为 d d d的情况下,图中所有顶点之间距离小于等于 d d d的顶点之间都连边,形成的无向图的连通分量的数量为1,或连通分量数量大于1时,需要满足小于等于 k k k。

求图中连通分量的数量,可以使用深搜或并查集求得。

解法2:最小生成树

同解法1,如果 k = 0 k=0 k=0,要将 k k k设为1。

对整个图执行kruskal算法,一开始图有 n n n个顶点,即有 n n n个连通分量,每选择一条边连接两个连通分量,连通分量数量减1。所以选择第 n − k n-k n−k条边后,当前图中连通分量的数量为 n − ( n − k ) = k n-(n-k)=k n−(n−k)=k。

在选择第 n − k n-k n−k条边前,可能有多次选择某一条边后,图中形成了环,而不选择该边。将这些边都添加进图中,每条边连接的两个顶点通过已选择的边已经连通,因此添加这些边并不会减少图中的连通分量的数量,图中连通分量的数量仍为 k k k。

根据解法1可知:本题需要求无线电通信距离为 d d d满足该条件的最小值,条件为:图中所有顶点之间距离小于等于 d d d的顶点之间都连边,形成的无向图的连通分量的数量为1,或连通分量数量大于1小于等于 k k k。

如果 d d d的值为kruskal算法中选择的第 n − k n-k n−k条边的权值,那么此时图中的连通分量数量为 k k k,满足条件。

如果 d d d的值小于kruskal算法中第 n − k n-k n−k条边的权值,那么相当于kruskal算法只能选择 n − k − 1 n-k-1 n−k−1条边,图中连通分量的数量为 n − ( n − k − 1 ) = k + 1 n-(n-k-1)=k+1 n−(n−k−1)=k+1,大于 k k k,不满足条件。

因此满足条件的 d d d的最小取值为kruskal算法求最小生成树过程中选择的第 n − k n-k n−k条边的权值。

【题解代码】

解法1:二分答案

  • 写法1:邻接矩阵,深搜求连通分量数量
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
#define N 505
double x[N], y[N], edge[N][N];
int n, k;
bool vis[N];
void dfs(int u, double d)
{
	vis[u] = true;
	for(int v = 1; v <= n; ++v) 
		if(edge[u][v] <= d && !vis[v])
			dfs(v, d);
}
bool check(double d)
{
	int cnt = 0;//连通分量数量 
	memset(vis, 0, sizeof(vis));
	for(int v = 1; v <= n; ++v) if(!vis[v])
	{
		cnt++;
		dfs(v, d);
	}
	return cnt <= k;//最少使用cnt个卫星通讯设备 使用的数量要<=k 
}
int main()
{
	double l = 0, r = 0;
	cin >> n >> k;
	if(k == 0)
		k = 1;
	for(int i = 1; i <= n; ++i)
		cin >> x[i] >> y[i];
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
		{
			edge[i][j] = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
			r = max(edge[i][j], r);
		}
	while(r-l > 1e-8)
	{
		double mid = (l+r)/2;
		if(check(mid))
			r = mid;
		else
			l = mid;
	} 	
	cout << fixed << setprecision(2) << l;
	return 0;
}
  • 写法2:邻接表,并查集求连通分量数量
cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 505;
struct Edge
{
	int v;
	double w;
};
vector<Edge> edge[N];
double x[N], y[N];
int n, k, fa[N];
void init(int n)
{
	for(int i = 1; i <= n; ++i)
		fa[i] = i;
}
int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
	fa[find(x)] = find(y);
}
bool check(double d)
{
	int cnt = 0;//连通分量数量 
	init(n);
	for(int u = 1; u <= n; ++u)
		for(Edge e : edge[u]) if(e.w <= d)
			merge(u, e.v);
	for(int i = 1; i <= n; ++i) if(fa[i] == i)
		cnt++;
	return cnt <= k;
}
int main()
{
	double l = 0, r = 0;
	cin >> n >> k;
	if(k == 0)
		k = 1;
	for(int i = 1; i <= n; ++i)
		cin >> x[i] >> y[i];
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
		{
			double w = sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
			edge[i].push_back(Edge{j, w});
			edge[j].push_back(Edge{i, w});
			r = max(r, w);//无线电距离最大值为最大边权,这时已经选择所有边 
		}
	while(r-l > 1e-8)
	{
		double mid = (l+r)/2;
		if(check(mid))
			r = mid;
		else
			l = mid;
	} 	
	cout << fixed << setprecision(2) << l;
	return 0;
}

解法2:最小生成树

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;
const int N = 505;
struct Edge
{
	int u, v;
	double w;
	bool operator < (const Edge &b) const
	{
		return w < b.w;
	}
};
vector<Edge> edges;
double x[N], y[N], ans;
int n, k, fa[N];
void init(int n)
{
	for(int i = 1; i <= n; ++i)
		fa[i] = i;
}
int find(int x)
{
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y)
{
	fa[find(x)] = find(y);
}
void kruskal()
{
	int edgeNum = 0;
	sort(edges.begin(), edges.end());
	for(Edge e : edges)
	{
		int u = e.u, v = e.v;
		if(find(u) != find(v))
		{ 
			merge(u, v);
			if(++edgeNum == n-k)//edgeNum增加后表示当前添加的是第edgeNum条边。 
			{
				ans = e.w;
				break;
			} 
		}
	}
}
int main()
{
	cin >> n >> k;
	if(k == 0)
		k = 1;
	init(n);
	for(int i = 1; i <= n; ++i)
		cin >> x[i] >> y[i];
	for(int i = 1; i <= n; ++i)
		for(int j = 1; j <= n; ++j)
			edges.push_back(Edge{i, j, sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))});
	kruskal();
	cout << fixed << setprecision(2) << ans;
	return 0;
}
相关推荐
会编程的土豆3 小时前
【leetcode hot 100】二叉树二叉树
数据结构·算法·leetcode
XiYang-DING3 小时前
【LeetCode】203. 移除链表元素(Remove Linked List Elements)
算法·leetcode·链表
墨神谕3 小时前
希尔排序详解
数据结构·算法·排序算法
胡楚昊3 小时前
Polar PWN (4)
linux·运维·算法
今儿敲了吗3 小时前
51| 数独
算法·深度优先·图论
半瓶榴莲奶^_^3 小时前
优先级队列(堆)
java·数据结构·算法
小樱花的樱花3 小时前
C++引用:高效编程的技巧
开发语言·数据结构·c++·算法
Yupureki3 小时前
《算法竞赛从入门到国奖》算法基础:动态规划-最长子序列
c语言·c++·算法·动态规划
沉鱼.443 小时前
进制转换题
开发语言·c++·算法