一本通题解——1434 Best Cow Fences

题目链接

一本通:http://ybt.ssoier.cn:8088/problem_show.php?pid=1434

自己OJ:http://47.110.135.197/problem.php?id=4459

题目

题目描述

给定一个长度为n的正整数序列A。求一个平均数最大的,长度不小于L的子序列。

输入

第一行,n和L;

n个正整数,表示A。

输出

一个整数,表示答案的1000倍(不用四舍五入,直接输出)。

输入样例

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

输出样例

6500

数据范围

1 ≤ n ≤ 100,000, 1 ≤ L ≤ n, 0 ≤ Ai ≤ 2000。

题目分析

本题是USACO 2003 Mar. Green原题。

分析

从题目可以知道,我们需要在序列 A 中,找出一个连续的子序列,该子序列的长度不小于 L,使得该子序列的平均数最大。

下面我们来分析一下输入样例,看看如何得出输出样例。

输入样例中我们可以知道,n=10, L=6 ,这样我们知道子序列的长度可能性有这么几种:6、7、8、9、10。

那么任务就变成,如下几个子任务:

1、找到所有长度为 6 的子序列中的最大值,然后求出平均值。

2、找到所有长度为 7 的子序列中的最大值,然后求出平均值。

3、找到所有长度为 8 的子序列中的最大值,然后求出平均值。

4、找到所有长度为 9 的子序列中的最大值,然后求出平均值。

5、找到所有长度为 10 的子序列中的最大值,然后求出平均值。

6、然后找到上面 5 个平均值的最大值,就是我们的结果。

所有长度为 6 的子序列

从样例我们知道,所有子序列如下:

1、子序列为 6, 4, 2, 10, 3, 8,那么总和为 33 。

2、子序列为 4, 2, 10, 3, 8, 5,那么总和为 32 。

3、子序列为 2, 10, 3, 8, 5, 9,那么总和为 37 。

4、子序列为 10, 3, 8, 5, 9, 4,那么总和为 39 。

5、子序列为 3, 8, 5, 9, 4, 1,那么总和为 30 。

那么最大的平均值为 39/6=6.5。

所有长度为 7 的子序列

从样例我们知道,所有子序列如下:

1、子序列为 6, 4, 2, 10, 3, 8, 5,那么总和为 38 。

2、子序列为 4, 2, 10, 3, 8, 5, 9,那么总和为 41 。

3、子序列为 2, 10, 3, 8, 5, 9, 4,那么总和为 41 。

4、子序列为 10, 3, 8, 5, 9, 4, 1,那么总和为 40 。

那么最大的平均值为 41/7=5.857。

所有长度为 8 的子序列

从样例我们知道,所有子序列如下:

1、子序列为 6, 4, 2, 10, 3, 8, 5, 9,那么总和为 47 。

2、子序列为 4, 2, 10, 3, 8, 5, 9, 4,那么总和为 45 。

3、子序列为 2, 10, 3, 8, 5, 9, 4, 1,那么总和为 40 。

那么最大的平均值为 47/8=5.875。

所有长度为 9 的子序列

从样例我们知道,所有子序列如下:

1、子序列为 6, 4, 2, 10, 3, 8, 5, 9, 4,那么总和为 51 。

2、子序列为 4, 2, 10, 3, 8, 5, 9, 4, 1,那么总和为 46 。

那么最大的平均值为 51/9=5.666。

所有长度为 10 的子序列

从样例我们知道,所有子序列如下:

1、子序列为 6, 4, 2, 10, 3, 8, 5, 9, 4, 1,那么总和为 52 。

那么最大的平均值为 52/10=5.2。

综上所述,我们知道这个最大的数据为 6500。

算法思路

暴力

如同上面的数据分析,就是列出所有组合,外层循环是从 L 到 n,内层循环就是遍历计算出所有长度为外层循环的总和,然后找出最大值。利用前缀和的技巧,可以降低对应的计算复杂度。即使这样,该算法的时间复杂度为 O(n^{2})

由于 n 的最大值为 100,000,自然这样的解决方案最终解决会是 TLE。

二分

使用二分查找,寻找是否存在长度不小于为 L 的子段的平均数大于二分答案。用二分枚举平均值 ave,数组里每个值都减去 ave,看是否有连续的超过 L 长度的区间使得这段区间的总和大于等于 0,如果能找到,那么说明这个平均值可以达到。

先每个 data[i] 减去 ave 得到 a[i],用 dp[i] 表示以 i 为结尾区间连续长度大于等于 L 的最大连续区间和,maxx[i] 表示以 i 为结尾的最大连续区间和,sum[i] 表示 1~i 的价值总和,那么 maxx[i]=max(maxx[i-1]+b[i],b[i]), dp[i]=maxx[i-f+1]+sum[i]-sum[i-f+1],判断是否有一个 i(i >= L)满足 dp[i]>=0。这样我们可以将复杂度变为 O(nlogn)。

代码细节

1、由于数据量比较大,可以使用 scanf 或者快读。可能使用 cin 会导致超时。

2、由于计算平均值会出现浮点数,所以二分中的三个变量(左边界、右边界和中间值)要使用 double。

AC参考代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 1e5+2;
double data[MAXN] = {};//保存数组A
double a[MAXN];//减去枚举值后的数组
double sum[MAXN];//前缀和数组
double maxx[MAXN];// i 为结尾的最大连续区间和
double dp[MAXN];// i 为结尾区间连续长度大于等于 L 的最大连续区间和

int main() {
    //读入n和l
    int n,l;
    scanf("%d%d", &n, &l);
    //读入数组A
    int i;
    double left = 2000;//左边界
    double right = 1;//右边界
    for (i=1; i<=n; i++) {
        //注意由于需要使用前缀和,所以第一个数据要设置为0
        scanf("%lf", &data[i]);
        left = min(left, data[i]);//更新左边界
        right = max(right, data[i]);//更新右边界
    }

    double eps = 1e-6;//精度
    while (right-left > eps) {
        //计算中间值
        double mid = (left+right)/2;

        //数组A的每个数据减去这个枚举值,并计算前缀和
        for (i=1; i<=n; i++) {
            a[i]=data[i]-mid;
            sum[i]=sum[i-1]+a[i];
            maxx[i] = max(a[i], maxx[i-1]+a[i]);
        }

        //遍历长度为L到n所有子序列
        double ans=sum[l];
        for (i=l+1; i<=n; i++) {
            dp[i]=maxx[i-l+1]+sum[i]-sum[i-l+1];
            if (ans<dp[i]) {
                ans=dp[i];
            }
        }

        //修正边界
        if (ans>=0) {
            left = mid;
        } else {
            right = mid;
        }
    }

    //输出
    printf("%d\n", (int)(right*1000));

    return 0;
}

动态规划

待补充。

  • 7
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值