二分:最佳牛围栏

题目链接

题目描述

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

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

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

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

输入格式
第一行输入整数 N 和 F ,数据间用空格隔开。

接下来 N 行,每行输出一个整数,第i+1行输出的整数代表,第i片区域内包含的牛的数目。

输出格式
输出一个整数,表示围起区域内每块地包含的牛的数量的平均值可能的最大值乘以1000得到的数值。

数据范围
1≤N≤100000
1≤F≤N
输入样例:

10 6
6 
4
2
10
3
8
5
9
4
1

输出样例:

6500

#include <iostream>
using namespace std;
constexpr auto arrmaxn = 100005;
int arr[arrmaxn];
double sum[arrmaxn];
int main(int argc, char **argv)
{
    int N, F;
    cin >> N >> F;
    for (int i = 1; i <= N; ++i)
        cin >> arr[i];
    double l = -1e6, r = 1e6, eps = 1e-5;
    while (r - l > eps)
    {
        double mid = (l + r) / 2.0;
        for (int i = 1; i <= N; ++i)
            sum[i] = sum[i - 1] + arr[i] - mid;
        double ans = -1e10, min_val = 1e10;
        for (int i = F; i <= N; ++i)
        {
            min_val = min(min_val, sum[i - F]);
            ans = max(ans, sum[i] - min_val);
        }
        if (ans >= 0)
            l = mid;
        else
            r = mid;
    }
    cout << static_cast<int>(r * 1000) << endl;
    return EXIT_SUCCESS;
}
  • 题目主要意思是,给定正整数序列,求一个平均值最大的、长度不小于 F 的连续子段。
  • 注意到题目是求可行方案的最大值,尝试采用二分法求取答案。二分查找平均值,计算当前平均值下是否可行,如果可行右缩查找空间,尝试寻找更大的平均值;反之若果不可行,向左缩小查找空间。
  • 在判断当前平均值是否可行时,需要找出验证长度不小于 F 的最大子段平均值是否大于等于当前二分值。如果直接去求这个长度不小于 F 的最大子段平均值,那么这个二分就白二分了,还不如直接求解。注意,现在需要的仅仅是验证当前二分值是否可以可行,只需验证下面这个不等式,
(arr[ 1 ] + arr[ 2 ] + …… + arr[ n ])/ n >= mid
  • 其中 mid 是当前二分的值,n 是子段的长度,必须是大于等于 F 的,上面不等式只要存在,证明当前二分值 mid 就是可行的。直接求解不等式的左边是不现实的,如果能直接求出来,也用不着费劲用二分法…
  • 那么,现在优化下上面的不等式,使之容易求解,将右侧的 mid 减到左侧,并且分配到分子的每一项;再将分母 n 移到右侧。现在不等式转化为,
(arr[ 1 ] - mid) + (arr[ 2 ] - mid)+ …… + (arr[ n ] -mid)>= 0
  • 验证现在这个不等式就容易多了,先将数列 arr 的每一项都减去 mid ,然后利用前缀和数组求解长度大于等于 F 的最大子段和,然后判断这个最大子段和是否是大于等于 0 的!
  • 通过二分将答案确定在一个很小的区间上后,为保证取值最大,选取区间的右端点乘以 1000 转化为 int 型输出。
     

END

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值