【题目链接】
【题目考点】
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;
}