CF940E Cashback 题解

给出一个长度为n的序列,和整数c.将其划分为任意段.对于每段,去掉前\(\frac{len}{c}\)(下取整)小的数,最小化剩下的数的和.

容易发现,对于小于c长度的段,不妨将其划分成长度为1,这样是等效的,且有利于最小化.对于长度大于2c的段,先把多出2c的部分拆成长度为1的段,然后把2c拆成两个c,这样一定不会变劣.原因在于较长段的最小和次小不可能比划分成两个较短段的最小值大.所以只有两种划分:长度为1,长度为c.(自己想到了这里)

考虑转移,容易发现如果选长度为1的段,会使答案直接多a_i,而选择长度为c的段,会使答案多sum-min,即区间和去掉一个最小值.用单调队列求出长度为c的所有段的最小值.转移即可.

还是要多注意单调队列写法.

#include <iostream>
#include <cstdio>
#include <queue>
#define int long long
using namespace std;
const int N = 100005, INF = 0x7fffffff;
int n, c, head = 1, tail = 0;
int a[N], q[N], g[N], sum[N], f[N];
signed main()
{
    ios::sync_with_stdio(false);
    cin >> n >> c;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    for (int i = 1; i <= n; ++i)
    {
        while (head <= tail && q[head] + c <= i)
            head++;
        while (head <= tail && a[q[tail]] >= a[i])
            tail--;
        q[++tail] = i;
        g[i] = a[q[head]];
    }
    for (int i = 1; i <= n; i++)
        if (i >= c)
            f[i] = min(f[i - 1] + a[i], f[i - c] + sum[i] - sum[i - c] - g[i]);
        else
            f[i] = f[i - 1] + a[i];
    cout << f[n] << endl;
}
posted @ 2021-11-14 19:41  Kinuhata  阅读(25)  评论(0)    收藏  举报