【贪心法】基站布置问题

问题描述:

海面上有一些船需要与陆地进行通信,需要在海岸线上布置一些基站。现将问题抽象为,在x轴上方,给出n条船的坐标 p 1 , p 2 , … , p n p_1, p_2, …, p_n p1,p2,,pn,其中 p i = ( x i , y i ) p_i = (x_i, y_i) pi=(xi,yi) 0 ≤ y i ≤ d 0≤y_i≤d 0yid 1 ≤ i ≤ n 1≤i≤n 1in,在 x x x 轴安放的基站可以覆盖半径为 d d d 的区域内的所有船只,问在 x x x 轴至少要安放几个基站才可以将 x x x 轴上方的船只都覆盖到。

基站布置问题

解决思路:

可以使用贪心法来解决。先按照每艘船到x轴距离为d的投影点(右侧那个)对船只进行排序。然后遍历每艘船,如果该船没有被任何一个基覆盖到,那么就在上述的那个点放置一个基站。
实质上就是让没覆盖到的船只处于新基站的最边缘位置,这样这个基站能贪到后续最多的基站。

一点补充(正确性分析):

如下图所示,假设船i之前的船已经分配了基站,那么现在我们要为船i分配基站。

虽然我们不知道船 i i i后面的船在什么位置,但是为了新分配的这个基站能有最大可能覆盖到其他船,显而易见我们会把这个新基站放在绿点处,让船 i i i处于新基站的边缘,也就是说新基站再往右移就已经覆盖不了船 i i i了。因此,我们可以看到在下图的例子中,新基站不仅覆盖了船 i i i,还贪到了船 j j j,而如果新基站放在红点就不能覆盖到船 j j j了。

对所有未被基站覆盖的船执行上述过程,就是一个贪心的过程。
基站布置问题例图

C++源代码:

//
//  main.cpp
//  CellPlacement
//
//  Created by 胡昱 on 2022/1/3.
//

#include <iostream>
#include <cstring>
#include <math.h>
using namespace std;

// 浮点误差
const double minINF = 0.00000000001;

// 最大船只数量
const int maxN = 10000;

// 记录船只的横纵坐标的数组,ships[i][0]为横坐标、ships[i][1]为纵坐标
double ships[maxN + 1][2];

// 记录基站的横坐标的数组,很明显最多也只能有和船只数量一样多的基站
// 可能翻译为 Base Station 好一点,但是这个比较简单
double cells[maxN + 1];
int cell_num;

// 共有n艘船只
int n;

// 基站可以覆盖半径为d的区域
int d;

// 对船只的位置进行排序
// 注意要按照可允许的最远基站点位置排序
int compareByPosition(const void* a, const void* b)
{
    double temp = ((double*)a)[0] + sqrt(d * d - ((double*)a)[1] * ((double*)a)[1]) - (((double*)b)[0] + sqrt(d * d - ((double*)b)[1] * ((double*)b)[1]));
    
    // 注意浮点数比较注意预留一定的精度判断
    if(-minINF <= temp && temp <= minINF) {
        return 0;
    }
    else if(temp < 0) {
        return -1;
    }
    else {
        return 1;
    }
}

// 判断该船只是否已被覆盖
bool isCover(double px, double py) {
    for(int ci = 0; ci < cell_num; ci++) {
        double temp = (px - cells[ci]) * (px - cells[ci]) + py * py - d * d;
        if(temp <= minINF) {
            return true;
        }
    }
    
    return false;
}

int main(int argc, const char * argv[]) {
    // 共m组测试用例
    int m;
    cin >> m;
    while((m--) > 0) {
        // 初始化
        memset(ships, 0, sizeof(ships));
        memset(cells, 0, sizeof(cells));
        cell_num = 0;
        
        // 输入船只数量和基站可覆盖范围半径
        cin >> n >> d;
        
        // 输入船只坐标
        for(int ni = 0; ni < n; ++ni) {
            cin >> ships[ni][0] >> ships[ni][1];
        }
        
        // 根据位置对船只进行排序
        qsort(ships, n, sizeof(double) * 2, compareByPosition);
        
        // 按顺序遍历每艘船只进行贪心法
        for(int ni = 0; ni < n; ++ni) {
            // 判断该船只有没有被覆盖,没有的话给它安排一个基站
            if(!isCover(ships[ni][0], ships[ni][1])) {
                cells[cell_num] = ships[ni][0] + sqrt(d * d - ships[ni][1] * ships[ni][1]);
                ++cell_num;
            }
        }
        
        // 输出结果
        cout << cell_num << endl;
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值