【前缀和】ABC267 C题题解

题目链接:

C - Index × A(Continuous ver.)

题意:

给定长度为m的数组A,找到一个给定长度为n的连续的A的子数组B使得i*B[i]的和最大。

分析:

数据范围为2e5,暴力枚举的 O\left ( {n^{2}} \right ) 做法是无法通过的,考虑 O\left (n\right )做法。虽然题目求的是i*B[i]的和,i是B数组的下标(从1开始),但是可以看成在A数组中找到长度为B的一个连续子序列,使得A[i]*i(这里i是A数组的下标)最大。

证明:

如果 \sum_{i}^{m}i*A[i] 为最大值,假设子序列从pos开始,那么将其下标i转化成B数组中的下标,(这就是题目要求)后 \sum_{i}^{m}(i-pos+1)*A[i] 也是最大的,因为其中累加的每一项的乘号后的数组A部分是不变的,变的只是它前面的系数,而这个系数始终是递增的,因此不影响原式的单调性,前面式子取最大值,后面的式子同步取最大值。

解答:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
#define endl '\n'
int a[N], q[N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    ll tmp = 0, ans = 0;
    int begin, end = m;
    int head = 1, tail = 1; //队列的头指针和尾指针
    //这里要手写队列因为题目给出的长度2e5超出了stl::queue的最大范围会RE
    for (int i = 1; i <= n; i++)
    {
        if (tail - head + 1 == m) //当队列的长度为m
        {
            int x = q[head++];  //拿出队头元素并弹出
            tmp -= x * (i - m); //队列向右滑动,当前值减去原队头对于项
        }
        q[tail++] = a[i]; //入队
        tmp += i * a[i];
        if (tmp > ans)
        {
            end = i; //记录最大值对应的子序列的终点下标
            ans = tmp;
        }
    }
    ans = 0, begin = end - m + 1;      //推得起点下标
    for (int i = begin; i <= end; i++) //算出转化成B数组下标后的答案
        ans += (i - begin + 1) * a[i];
    cout << ans;
    return 0;
}

这个解法是能通过题面给出的两个样例的,但是提交后发现仅对了两个点,再一看题目的数据范围,原来数组A中可能有负数,与正数的情况恰好相反,这也就意味着单调性不成立。此解法out

(其实也并不需要额外使用队列存储,可直接通过下标计算把头的值减去)

但是!如果数组A全为正数,这个解法就是正确的。

接下来我们看正解。首先要注意到这样一个规律:

这就意味着可以通过以 i 为字串起点的累加值(题目要求)推出下一个即 i+1 的累加值 

记以 i 为字串起点的 \sum_{i=1}^{m}i*b[i]为ts[i],得到递推关系:

ts[i]=ts[i-1]+m*a[i-1+m]-\sum_{i=1}^{m}a[i]

发现后面这个\sum_{i=1}^{m}a[i]可以用前缀和优化。

我们初始化求出以1为起点的ts[1]就可在线性时间推得所有的子序列ts值,在这个过程中更新最大值。

注意前缀和数组和答案要开long long!

但是!这样还是没过,因为m*a[i-1+m] 这项也会溢出int范围,这个坑我找了很久。为了省去强制类型转化a数组也直接开long long!所以说还是不开long long见祖宗啊。

AC代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
#define endl '\n'
ll a[N], fsum[N], ts[N];
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(0);
    int n, m;
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        fsum[i] = fsum[i - 1] + a[i - 1]; //求前缀和,这里包含的是到第i-1项的和
    }
    for (int i = 1; i <= m; i++)
        ts[1] += i * a[i];
    ll ans = ts[1]; //初始化ans
    for (int i = 2; i <= n - m + 1; i++)
    {
    	//注意下标QaQ
        ts[i] = ts[i - 1] - (fsum[i - 1 + m] - fsum[i - 1]) + m * a[i - 1 + m];
        ans = max(ans, ts[i]);
    }
    cout << ans;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值