CF1661D Progressions Covering 题解

题目传送门

前言

本篇题解亮点在于解释如何一遍从后往前扫一边双重差分。

解法

贪心,从右往左依次满足每个数。至于贪心的正确性其他题解已经讲得很清楚了,不再赘述。 而题目中将数组 $a$ 加到 $a_i\ge b_i$ 等价于把 $b_i+\Delta b_i\le0$。

重点是如何维护贪心过程中进行的区间减等差数列。 暴力减等差数列十分麻烦,有没有替代的方法?

先看这张图,数组 $\Delta b$ 初始时全是 $0$,我们减去一个等差数列(用大括号括起来的)后变成:$$ \cdots~0~\overbrace{-d~{-2d}~{-3d} ~{-4d}}^{[l,i]}~0\cdots $$

对它进行差分两次(从右往左):

下标 $l-2$ $l-1$ $l$ $l+1$ $i-1$ $i$ $i+1$
数组 $\Delta b$ $0$ $0$ $-d$ $-2d$ $-3d$ $-4d$ $0$
差分数组 $0$ $+d$ $+d$ $+d$ $+d$ $-4d$ $0$
双重差分数组 $c$ $-d$ $0$ $0$ $0$ $+5d$ $-4d$ $0$

我们发现,只要在 $c$ 上标记 $3$ 个位置即可:$l-2,i-1,i$。 我们可以用 $c$ 对应 $\Delta b$,需要 $\Delta b$ 时进行对 $c$ 进行两次前缀和还原出 $\Delta b$。

但是麻烦还没完,我们要一边维护 $c$,一边算出 $\Delta b_i$ 的值。

将操作分为三部分:

  1. 进行还原,通过 $c$ 算出 $\Delta b_i$,即代码中的 sum2
  2. 计算 $b_i+\Delta b_i$ 和应该贪心地减去几个等差数列。
  3. 如果要减,那么对 $c$ 进行标记,表示出减去的等差数列,并修正一些中间变量如代码中的 sum1sum2。因为如果要减,就要更新 $c_i$,而在第 $1$ 步中根据 $c_i$ 算的 sum1sum2(即 $\Delta b$)也要修正。

具体见代码。

代码

#include <cstdio>
#include <iostream>
using namespace std;

typedef long long ll;
const int N=300005;
int n,k;
ll ans,sum1,sum2;
ll b[N],c[N];// 原数组和双重差分数组。注意双重差分数组两次前缀和(“解码”)后
// 得出的是原数组要减去的值(即差值)。

int main()
{
    scanf("%d %d",&n,&k);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",b+i);
    }
    for(int i=n;i>0;i--)// 从后往前扫
    {
        // 还原
        sum1+=c[i];// 一次前缀和
        sum2+=sum1;// 两次前缀和
        // 计算要减去多少个等差数列(用 d 表示)。注意不要越界。
        ll l=max(1,i-k+1),len=i-l+1,d=(b[i]+sum2+len-1)/len;
        if(d>0)// 如果要减
        {
            ans+=d;// 记录答案
            // 标记 c 数组。
            if(l-2>0)
            {
                c[l-2]-=d;
            }
            c[i-1]+=(len+1)*d;
            // 修正两个前缀和。可以看上面的表格理解。
            sum1-=len*d;
            sum2-=len*d;
        }
    }
    printf("%lld",ans);
    return 0;
}
posted @ 2023-10-03 21:42  Po7ed  阅读(15)  评论(0编辑  收藏  举报  来源