P1419 寻找段落 二分答案+单调队列

好久没写题了,拼死拼活才写了道绿题。

P1419 寻找段落

题目中要我们求一段数列的平均值,这段数的长度被限定在S-T之间。

那么我们可以用二分法枚举答案,之后来寻找区间,如果这些区间之中有一个区间中的数减去二分出的答案的和要比二分出的答案要大,

则说明这个二分答案比我们要求的真正的答案要小,则改变区间重新枚举。

如果没有任何一个区间的数减去二分出的答案的和要比二分出的答案要大,则说明这个二分答案比我们要求的真正的答案要大。

之后的问题就是如何枚举区间的问题。

我们利用每个数减去二分出的数放到一个新的数组中,再利用前缀和来求区间的和。

然后利用单调队列来保存最小的一个前缀和即可。

#include<iostream>
#include<deque>
#include<stdio.h>
#include<algorithm>
using namespace std;
int n, s, k, a[100005];
double sum[100005],now[100008];
double L = -10000, R = 10000;
bool check(double x) {
    for (int i = 1; i <= n; i++)
        now[i] =(double) a[i] - x;//把这些数保存起来
    sum[0] = 0;
    for (int i = 1; i <= n; i++)
        sum[i] = sum[i - 1] + now[i];//前缀和
    deque<int> q;
    for (int i = s; i <= n; i++) {//区间长度S-T所以我们从S开始枚举
        while (!q.empty() && sum[q.back()] > sum[i-s])//搞一个单调递增队列,如果最后一个数比枚举的数大的话就删掉,这样保证队首的前缀和最小
            q.pop_back();
        q.push_back(i - s);
        while (q.front() < i - k)//如果区间长度大于T了就pop掉,所以其实没有必要将S-T都枚举一遍,只要保证队首的前缀和最小就好了
            q.pop_front();
        if (sum[i]- sum[q.front()]>= 0)//求出区间和,如果大于0说明实际答案比枚举答案要大
            return 1;
    }
    return 0;
}
int main() {
    cin >> n >> s >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }
    while (R - L > 1e-5) {//如果相差很小就可以直接输出了
        double mid = (L + R) / 2;
        if (check(mid))
            L = mid;
        else R = mid;
    }
    printf("%.3lf", L);
    return 0;
}

 

posted @ 2021-02-14 22:11  redintonc  阅读(49)  评论(0)    收藏  举报