P1991

无线通讯网

题目描述

国防部计划用无线网络连接若干个边防哨所。22 种不同的通讯技术用来搭建无线网络;

每个边防哨所都要配备无线电收发器;有一些哨所还可以增配卫星电话。

任意两个配备了一条卫星电话线路的哨所(两边都有卫星电话)均可以通话,无论他们相距多远。而只通过无线电收发器通话的哨所之间的距离不能超过 DD,这是受收发器的功率限制。收发器的功率越高,通话距离 DD 会更远,但同时价格也会更贵。

收发器需要统一购买和安装,所以全部哨所只能选择安装一种型号的收发器。换句话说,每一对哨所之间的通话距离都是同一个 DD。你的任务是确定收发器必须的最小通话距离 DD,使得每一对哨所之间至少有一条通话路径(直接的或者间接的)。

输入格式

第一行,22 个整数 SSPPSS 表示可安装的卫星电话的哨所数,PP 表示边防哨所的数量。

接下里 PP 行,每行两个整数 xxyy 描述一个哨所的平面坐标 (x,y)(x, y),以 km 为单位。

输出格式

第一行,11 个实数 DD,表示无线电收发器的最小传输距离,精确到小数点后两位。

输入输出样例

输入样例 #1

text
2 4
0 100
0 300
0 600
150 750

输出样例 #1

text
212.13

说明/提示

数据范围及约定

  • 对于 20%20\% 的数据:P=2P = 2S=1S = 1
  • 对于另外 20%20\% 的数据:P=4P = 4S=2S = 2
  • 对于 100%100\% 的数据保证:1S1001 ≤ S ≤ 100S<P500S < P ≤ 5000x,y100000 ≤ x,y ≤ 10000

题解

1 篇题解

登录后即可使用 Markdown 发布题解。

登录 / 注册
YuyiLv.6👑 站长管理员
P1991 无线通讯网 题解 Author: Yuyi 题意分析 题意可以转化为:给定 $P$ 个哨所(可以看作平面上的 $P$ 个点),我们要在任意两点之间连一条边,边的权值为这两点之间的欧几里得距离。 现在我们手里有 $S$ 个“卫星电话”。拥有卫星电话的哨所之间互相通信是“免费”的(距离不受限)。这意味着这 $S$ 个分配了卫星电话的哨所,等价于允许整…
点击展开完整题解点击收起题解

P1991 无线通讯网 题解

Author: Yuyi

题意分析

题意可以转化为:给定 PP 个哨所(可以看作平面上的 PP 个点),我们要在任意两点之间连一条边,边的权值为这两点之间的欧几里得距离。 现在我们手里有 SS 个“卫星电话”。拥有卫星电话的哨所之间互相通信是“免费”的(距离不受限)。这意味着这 SS 个分配了卫星电话的哨所,等价于允许整个图存在 SS 个互不连通的“连通块”而依然能互相通信。 题目要求所有普通无线电收发器的最大距离 DD 尽量小。这不就是妥妥的 Kruskal 最小生成树 变种吗!我们只需要将所有边按权值从小到大排序,当连通块数量刚好降到 SS 的时候,最后加进来的那条边的权值就是我们要求的 DD

正解推导过程

由于任何两个哨所都可以用无线电相连,我们首先需要构建一张完全图:

  1. 建图:枚举所有的点对 (i,j)(i, j),计算它们之间的直线距离,存入边集数组中。边数总共约 P(P1)21.25×105\frac{P(P-1)}{2} \approx 1.25 \times 10^5 条。
  2. Kruskal 贪心加边:将所有的边按权值从小到大排序。
  3. 并查集维护连通块:起初有 PP 个节点,也就是 PP 个独立的连通块。每次通过并查集 find 找到两个端点,如果不在同一个连通块,就合并它们。合并一次,整个图的连通块数量就会减一。
  4. 结束条件:起初连通块个数为 PP,要让连通块个数降到 SS,我们恰好需要成功加入 PSP - S 条边。加入的第 PSP - S 条边,就是我们所需的最长无线电通信距离 DD

正解代码及分析

cpp
#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define eps 1e-9
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define lowbit(a) (a)&(-a)
using namespace std;
const int maxn = 125005; // P<=500,最大边数约为125000,统一采用maxn开辟
const int mo = 998244353;
const double pi = acos(-1.0);
template <typename T>
inline void read(T &X)
{
    X = 0;int w = 0; char ch = 0;
    while(!isdigit(ch)) {w |= ch == '-' ;ch = getchar();}
    while(isdigit(ch)) X = X * 10 + (ch^48),ch = getchar();
    if(w) X = -X;
}
int s,p;
int x[maxn],y[maxn];
struct Edge
{
    int u,v;
    double w;
}e[maxn];
bool cmp(Edge A,Edge B)
{
    return A.w < B.w;
}
int fa[maxn];
int find(int u)
{
    if(u == fa[u]) return u;
    return fa[u] = find(fa[u]);
}
void solve()
{
    read(s),read(p);
    rep(i,1,p) read(x[i]),read(y[i]);
    if(s >= p)
    {
        cout << fixed << setprecision(2) << 0.00 << endl;
        return;
    }
    int m = 0;
    rep(i,1,p)
    {
        rep(j,i+1,p)
        {
            m++;
            e[m].u = i;
            e[m].v = j;
            e[m].w = sqrt((double)(x[i]-x[j])*(x[i]-x[j]) + (double)(y[i]-y[j])*(y[i]-y[j]));
        }
    }
    rep(i,1,p) fa[i] = i;
    sort(e+1,e+1+m,cmp);
    int cnt = 0;
    double ans = 0;
    rep(i,1,m)
    {
        int fu = find(e[i].u);
        int fv = find(e[i].v);
        if(fu != fv)
        {
            fa[fu] = fv;
            ans = e[i].w; // 从小到大排序,最后一次记录的直接就是最大边权
            cnt++;
        }
        if(cnt == p-s) break;
    }
    cout << fixed << setprecision(2) << ans << endl;
    return;
}
int main()
{
    int T=1;
//  freopen("mul.in","r",stdin);
//  freopen("mul.out","w",stdout);
//  read(T);
    while(T--) solve();
    return 0;
}

复杂度分析

  • 时间复杂度:计算所有点对之间的距离需 O(P2)O(P^2);然后对最多 P22\frac{P^2}{2} 条边进行排序,耗时约 O(P2logP)O(P^2 \log P);最后使用并查集扫描边合并,因为路径压缩的存在几乎是线性的 O(P2)O(P^2)。综合来看时间复杂度为 O(P2logP)O(P^2 \log P),在 P500P \le 500 面前毫无压力。

另一种解法:二分答案 + 并查集检验

这道题确实可以直接用二分来做!因为“使得每一对哨所之间至少有一条通话路径的最短收发器距离 DD”是一个极其典型的最大值最小化问题,具有单调性:如果距离 DD 能够满足要求,那么所有比 DD 大的距离一定也都满足;如果 DD 不满足,所有比 DD 小的距离更不满足。

  1. 二分边界:距离最小是 00,最大是网格对角线大约 10000×214142.210000 \times \sqrt{2} \approx 14142.2。我们直接在 [0,20000][0, 20000] 范围内二分实数 DD
  2. check(mid) 函数设计:对于给定的距离上限 mid,我们遍历所有点对。如果两点之间的距离 mid\le mid,就用并查集将它们合并。由于起初有 PP 个独立连通块,每次成功合并就让连通块数量 cnt--。最后检查 cnt 是否 S\le S 即可。如果剩下的连通块数量 S\le S,说明借用 SS 个卫星电话足以连通全局,这个 mid 是可行的。

法二代码及分析

cpp
#include <bits/stdc++.h>
#define ll long long
#define inf 0x3f3f3f3f
#define eps 1e-9
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define dep(i,a,b) for(int i=(a);i>=(b);i--)
#define lowbit(a) (a)&(-a)
using namespace std;
const int maxn = 505; // 二分法不存边,仅存点即可
const int mo = 998244353;
const double pi = acos(-1.0);
template <typename T>
inline void read(T &X)
{
    X = 0;int w = 0; char ch = 0;
    while(!isdigit(ch)) {w |= ch == '-' ;ch = getchar();}
    while(isdigit(ch)) X = X * 10 + (ch^48),ch = getchar();
    if(w) X = -X;
}
int s,p;
int x[maxn],y[maxn];
int fa[maxn];
int find(int u)
{
    if(u == fa[u]) return u;
    return fa[u] = find(fa[u]);
}
bool check(double mid)
{
    rep(i,1,p) fa[i] = i;
    int cnt = p;
    rep(i,1,p)
    {
        rep(j,i+1,p)
        {
            double dist = sqrt((double)(x[i]-x[j])*(x[i]-x[j]) + (double)(y[i]-y[j])*(y[i]-y[j]));
            if(dist <= mid)
            {
                int fu = find(i);
                int fv = find(j);
                if(fu != fv)
                {
                    fa[fu] = fv;
                    cnt--;
                }
            }
        }
    }
    return cnt <= s;
}
void solve()
{
    read(s),read(p);
    rep(i,1,p) read(x[i]),read(y[i]);
    if(s >= p)
    {
        cout << fixed << setprecision(2) << 0.00 << endl;
        return;
    }
    double l = 0, r = 20000.0;
    while(r - l > eps) // 满足精度eps = 1e-9后退出
    {
        double mid = (l + r) / 2.0;
        if(check(mid)) r = mid;
        else l = mid;
    }
    cout << fixed << setprecision(2) << r << endl;
    return;
}
int main()
{
    int T=1;
//  freopen("mul.in","r",stdin);
//  freopen("mul.out","w",stdout);
//  read(T);
    while(T--) solve();
    return 0;
}
  • 二分法复杂度:二分实数在 eps = 1e-9 精度下,循环大约需要跑 506050 \sim 60 次。每次 check 需要 O(P2)O(P^2) 的时间去双重循环判断点对,耗时约 1.25×1051.25 \times 10^5 次。综合时间复杂度为 O(kP2)O(k \cdot P^2)kk 为常数 6060),运算量约 7.5×1067.5 \times 10^6,与 Kruskal 的 O(P2logP)O(P^2 \log P) 不相上下,同样能稳妥 AC。而且二分法不需要提前把所有边存下来,空间复杂度仅为 O(P)O(P),连数组都可以放心降到 maxn = 505

讨论

0 条讨论

登录后即可使用 Markdown 发起讨论和回复。

登录 / 注册

还没有讨论,来发起第一条吧。