《算法竞赛进阶指南》——最佳牛围栏

1.题目描述

农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于 1 头,也不会超过 2000 头。

约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。

围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。

在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。

输出一个整数,表示平均值的最大值乘以 1000 再 向下取整 之后得到的结果。

2.题目分析

方案1

首先,如果我们用cows[N]来存储每个田地的牛的数量的话,要求田地i到田地j中牛的数量,我们很容易想到前缀和。设前缀和数组为sum[N],那牛的数量为:sum[j]-sum[i-1]

但注意到题目中说田地的数量为“至少包含F块地”,最简单的做法就是把所有数量大于F的连续区间都求一遍:

for(int i=1;i+f-1<=n;i++){
        for(int j=f;i+j-1<=n;j++){
            if((a[i+j-1]-a[i-1])/j>res) res=(a[i+j-1]-a[i-1])/j;
        }
    }

然后再用二分法把不大于这个浮点数的1000倍的最大整数求出来:

 while(left<right){
        int mid=(left+right+1)/2;
        if(mid<=res){
            left=mid;
        }
        else right=mid-1;
    }

但是运行会超时…事实上,对于输入的n和f,求前缀和的时间复杂度为O(n),将所有长度大于f的区间都遍历一遍,那比如i=1时,要从1遍历到n-f+1,n-f+1次,在i=n-f+1时为1次,所以消耗的时间为T=1+2+3+……+n-f+1=(n-f)(n-f-1)/2=O(n²),所以是很花费时间的。

方案2

在基于二分法的前提下,这题的思路居然是给出一个平均值avg,看是否有大于F的区间的平均值大于avg,从而不断缩小范围。

我们知道,在整个区间上,左边的一部分满足某个性质1,右边的一部分满足性质2,我们可以用二分法求出分界点。结合到题目给出的,每个田地牛的最小值为1,最大值为2000,也就是说最大均值一定∈[1,2000],那么在这个数轴上,小于等于avg的为左半边部分,大于avg的为右半边。

这个单调性是显然的,若我给出一个均值a,我们不存在区间长度大于等于F的连续区间的均值大于等于a,那么我们就可以把范围缩小为[left,a);相反,若存在区间长度大于等于F的连续区间均值大于等于a,那么最大均值avg一定满足avg>=a,那么我们又可以把范围缩小为[a,right]。

在这里插入图片描述
那现在的一个问题是,对于给定的mid,我如何判断mid属于左半边还是右半边?也就是说,是否存在长度大于等于F的区间的均值大于等于mid?

我们现在假设区间cows[i:j]的均值是会大于等于mid的,那么(sum[j]-sum[i-1])/(j-i)≥F,即sum[j]-sum[i-1]≥mid(j-i+1),这时候我们若将cows[N]都减去平均值mid,得到新的sum,那么sum[j]+j*mid-sum[i-1]-(i-1)*mid=sum[j]-sum[i-1]+(j-i+1)*mid≥mid(j-i+1),即sum[j]-sum[i-1]≥0

所以,我们会想,是否要让i从1遍历到n-f+1,区间长度从f遍历到n-i+1?
我们需要注意到,当j等于F时,我只有一个i可以去比较,即i=1;当j等于F+1时,我可以与两个i比较,即i=1或i=2,同样的,当j等于F+n时,我可以与i=1,2,……n比较。但注意,sum[j]-sum[i-1]≥0是一个存在问题,对于j=i+f-1,我只需要判断sum[j]是否会大于min{ sum[0],sum[1]……sum[i-1]}因而每次j向后移动一位,只需要因为i的选择可以同样往后移动一位而更新最小值即可。即minv=min{minv,sum[i-1]},此时sum[j]只需要和minv进行比较。非常巧妙…

接下来就是二分法,具体代码如下:

#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int cows[N];
double sum[N];//前缀和
int n,f;
bool check(double mid){
    double minv=0;
    for(int i=1;i<=n;i++){
        sum[i]=sum[i-1]+cows[i]-mid;
    }
    for(int i=0,j=f;j<=n;i++,j++){//sum[j]总是和前sum[1:j-f+1]中的最小值比较,一定保证了长度大于f
        minv=min(sum[i],minv);
        if(sum[j]>=minv) return true;
    }
    return false;
}
int main(){
    cin>>n>>f;
    for(int i=1;i<=n;i++){
        scanf("%d",&cows[i]);
    }
    double left=1;
    double right=2000;
    while(right-left>1e-5){
        double middle=(left+right)/2;
        if(check(middle)){//如果满足条件
            left=middle;
        }
        else{
            right=middle;
        }
    }
    `cout<<int(right*1000)`;
}

关于代码实现,有以下几个问题需要注意:

1.这里不是整数上的二分问题,所以不可以写成

if(check(middle)){//如果满足条件
            left=middle+1;
        }

那另一种写法也是不可以的:

while(right-left>1e-5){
        double middle=(left+right+1)/2;
        if(check(middle)){//如果满足条件
            left=middle;
        }
        else{
            right=middle-1;
        }
    }

因为这根本就不是整数上的二分问题,你right=middle-1,万一答案就在(middle-1,middle)之间呢?所以直接用middle更新left与right。

2.题目有说到向下取整,将最后改成cout<<int(left*1000) 是不对的,那为什么会这样?

举个例子,比如答案为6.9876543……,在精度控制在1e-5的情况下,假设得到的区间为(6.987652,6.987655),这里输出的都是6987没有问题;但是如果答案本身比1e-3精度小呢》比如答案为6.78,那么得到的区间就会为(6.77999,6.78001),用left输出就错了。

所以说,当答案的精度比1e-3更小时,你的左边区间只能将我小数点后最后一位减1,后面补上9,用左边的区间就会出错。而右边的区间会在1e-3位后面补上很小很小的数,但是我们用int转化,把这些都舍掉,正好可以得到正确答案。当然,答案的精度比1e-3更大时,就左右都可以,与向下取整契合。

总结

我们需要知道,为什么这一题会从二分法的角度切入?

我认为最主要的原因是题中的这一条件:“其数量不会少于 1 头,也不会超过 2000 头”,这一个信息就告诉我们答案一定在[1,2000]之间,我都已经知道答案的范围了,我就可以尝试用二分法解决。

假设每个田地的牛的数量是未知的,那就没有办法用二分法,我不能够确定right的初始值。

所以一旦确定用二分法,把max_avg设为我的分界点,我们就需要找到分界点左边和右边的性质,这时候再去定义check(int x) 函数。

那本题又一个难点就是check函数中的双指针算法,因为我们将问题转化位判定问题,存在问题,所以我们就可以像分析时那样,将i从i+f-1开始举例,最后发现对于j=f+n,我只要大于min{ sum[0],sum[1]……sum[n-1]},而minv又可以递归的求解,找到这一个关键的地方。

感谢AcWing平台

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用中提到了一个名为isPointInPolygon.js的函数,该函数用于判断一个点是否在一个多边形内部。函数接受三个参数:ALon是点的经度,ALat是点的纬度,APoints是多边形的顶点坐标数组。函数通过遍历多边形的边来判断点与边的关系,如果点在边的水平平行线之间且在边的左射线上,那么就可能与该边相交。最后,通过判断与多边形边相交的次数,如果是奇数次则表示点在多边形内部,否则表示点在多边形外部。 引用提到了一个Leaflet插件leaflet.geofencer,它是一个简单的地理围栏(多边形创建)工具。通过使用该插件,您可以在Leaflet地图上创建多边形围栏,并使用isPointInPolygon.js函数来判断一个点是否在该围栏内部。 综上所述,如果您想实现Leaflet围栏算法,您可以使用isPointInPolygon.js函数来判断一个点是否在多边形内部,并结合Leaflet地图和leaflet.geofencer插件来创建和管理围栏。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [leaflet 给定坐标点,判断是否在某区域范围内 地理围栏算法](https://blog.csdn.net/zkcharge/article/details/117168220)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [leaflet.geofencer:一个基于 Leaflet 的简单地理围栏(多边形创建)工具](https://download.csdn.net/download/weixin_42117116/19846128)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [Leaflet中原生方式实现测量面积](https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/122358946)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值