P1991 无线通讯网 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
题目其实不难,数据也很水。主要也是套最小生成树的模板,但是这题也算是个变式了,思考和处理边的思路还是很有价值的。
题目说的很复杂,其实就是有p个顶点,每个顶点可以和自身半径D以内的顶点相互链接,同时还可以选出其中的s个顶点对他们标记,标记的两个顶点可以相互链接。求使所有顶点相连的最小半径D是读多少。
思路:我们可以先不管标记的事,就求出要把所有顶点连起来D最小应该是多少,这个很简单每条边的权就是距离,这也就是最小生成树问题,由kruskal我们可以知道最小的D应该是最小生成树的最长边,也就是kruskal算法最后选择的那条边。
最小生成树 Kruskal 和 Prim算法及堆优化_Brokenrivers的博客-CSDN博客
但是D还能不能更小呢?是可以的,因为题目给了我们让任意两个顶点相连的能力,也就是可以任意的链接s-1个联通块,所以我们只需要对kruskal算法改进一下,当已经链接的边(联通块个数)达到p-s的时候停止,然后输出最后添加的边就行了。
然后就是这题其实只给了顶点,我们只需要两两算出距离就能得到每一条边了
#include<bits/stdc++.h>
#pragma warning (disable:4996);
#define ll long long
#define int ll
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N = 2e5 + 10;
int S, P, k, q, T;
int a, b, c;
int pre[N];
void init(int k) {
for (int j = 1; j <= k; j++) {
pre[j] = j;
}
}
inline int fnd(int k) {
return pre[k] == k ? k : pre[k] = fnd(pre[k]);
}
void join(int a, int b) {
pre[fnd(a)] = fnd(b);
}
struct node {
int x, y;
}v[N];
struct edge {
int from, to;
double pw;
int operator<(const edge& a)const {
return pw < a.pw;
}
}edg[N];
int esum = 0;//记录边数
double kruskal() {
int e= esum;
double sum = 0;
int cnt = 0;
int ok = 0;
init(P);
sort(edg + 1, edg + e + 1);
for (int j = 1; j <= e; j++) {
int t1 = fnd(edg[j].from), t2 = fnd(edg[j].to);
if (t1 != t2) {
pre[t1] = t2;
cnt++;
if (cnt == P -S) {
int ok = 1;
sum = edg[j].pw;
break;
}
}
}
return sum;
}
double disa(node a, node b) {
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int used[510][510] = { 0 };
signed main() {
std::ios::sync_with_stdio(0); cout.tie(0);
#ifndef ONLINE_JUDGE
freopen("out.txt", "w", stdout);
#endif
cin >> S >> P;
for (int j = 1; j <= P; j++) {
cin >> v[j].x >> v[j].y;
}
for (int j = 1; j <= P; j++) {//处理得边
for (int i = 1; i <= P; i++) {
if (j != i) {
if (used[j][i] == 0) {///注意重复边
edg[++esum].from = j;
edg[esum].to = i;
edg[esum].pw = disa(v[j], v[i]);
used[j][i] = 1;
used[i][j] = 1;
}
}
}
}
printf("%.2lf\n", kruskal());
}