牛客多校2021(四)J.Average(二分、前缀和)

  • 题目:Average

  • 题意:给出两个序列a、b,定义一个矩阵w,w[i][j] = a[i] + b[j],求该矩阵中宽至少为x,长至少为y的子矩阵元素之和的平均值最大能为多少。

  • 思路:二分 + 前缀和(与最佳牛围栏相似)

  • 解析:经过公式推导可得:

\[\begin{align*} Avg &= \sum_{i=l_1}^{r1}\sum_{j=l_2}^{r2}w[i][j] / (r_1-l_1+1)(r_2-l_2+1) \\ &= \sum_{i=l_1}^{r1}\sum_{j=l_2}^{r2}(a_i+b_j)/(r_1-l_1+1)(r_2-l_2+1) \\ &= (\sum_{i=l_1}^{r1}a_i\times(r_2-l_2+1) + \sum_{j=l2}^{r2}b_j\times(r1-l1+1))\div(r_1-l_1+1)(r_2-l_2+1)\\ &=\sum_{i=l_1}^{r1}a_i/(r_1-l_1+1) + \sum_{j=l_2}^{r2}b_j/(r2-l2+1) \end{align*} \]

​ 不难发现,此时需要求+号两边的式子最大值即可得到Avg的最大值,实际上就是找出a序列中平均值最大的一段(平均值为avgA)与b序列中平均值最大的一段(平均值为avgB)之和即为答案。举例avgA二分求解过程(avgB同理可得):

\[\begin{align} &\sum_{i=l_1}^{r1}ai/(r_1-l_1+1)\geq avgA \\ &\sum_{i=l_1}^{r1}a_i \geq avgA\times(r_1-l_1+1) \\ &\sum_{i=l_1}^{r1}a_i-avgA\geq0 \end{align} \]

​ 接着利用前缀和+双指针来寻找是否存在一段子段和满足上诉条件,使得该字段和-枚举的平均值avgA*(子段和元素个数)>= 0的情况出现,在这里重点写一下之前第一次写最佳牛围栏的时候的疑惑:

​ 在代码中的sum[i]实际上不一定保证随着i增大而增大,因为二分枚举的平均值avg可能比该子段中任意一个数大,但是为了保证该子段至少有x/y个数,那么我们需要设置一个双指针i, j(i为前指针从0开始,j为后指针从x/y开始)然后记录前指针扫描过的最小值minV,这样后指针j扫描的值sum[j]-minV肯定保证该子段长度至少为x/y,并且可以找到所有子段中平均值最大tempAvg的那个子段(j一直遍历到序列最后一个元素),若tempAvg > avg,则二分的区间可以往右区间缩小(枚举更大的avg)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 1e5 + 5;
int n, m;
double sum[N];
int check(double t[], double avg, int len, int num)
{
    double minV = 0x3f3f3f3f;
    for(int i = 1; i <= len; i++) sum[i] = sum[i-1] + t[i] - avg;
    for(int i = 0, j = num; j <= len; i++, j++)
    {
        minV = min(minV, sum[i]);
        if(sum[j] - minV >= 0) return 1;
    }
    return 0;
}

double solve(double t[], int len, int num)
{
    double l = 0, r = 1e6;
    for(int i = 1; i <= 100; i++)
    {
        double mid = (l + r) / 2;
        if(check(t, mid, len, num)) l = mid;
        else r = mid;
    }
    return l;
}

int main()
{
    double a[N], b[N];
    int x, y;
    cin >> n >> m >> x >> y;
    for(int i = 1; i <= n; i++) cin >> a[i];
    for(int i = 1; i <= m; i++) cin >> b[i];
    double res = solve(a, n, x) + solve(b, m, y);
    printf("%.10lf\n", res);
    return 0;
}

上一篇:平均负载(load average)


下一篇:java算法题-将数组分成和相等的三个部分