Progressions Covering 题解

upd

  • 2023-10-3 对二次差分进行补充解释。感谢 pig_pig_ 的指正。

题意

给定长度为 n n n 的数组 a a a b b b a a a 数组初始为 0 0 0,每次可以执行一个操作:选定一段长度为 k k k 的区间,区间左端点大于 0 0 0,右端点小于等于 n n n,将区间内第一个元素加 1 1 1,第二个元素加 2 2 2,以此类推,给定 n n n,数组 b b b k k k,求满足 a i ≥ b i a_i\ge b_i aibi 的最小操作次数,其中 1 ≤ i ≤ n 1\le i\le n 1in

思路

考虑贪心算法。

如果 a i < b i a_i<b_i ai<bi,即没有满足条件,则最优的操作方案显然为将 a i a_i ai 放在长度为 k k k 的操作区间尽量末尾处。这样操作可以使操作区间产生的影响最大化,且 a i a_i ai 的增加程度最大化。

做法就出来了。考虑从后往前做,假设当前做到了第 i i i 个元素,且该元素没有满足条件, [ i + 1 , n ] [i+1,n] [i+1,n] 的元素都满足条件了。我们选择一个前面所说的最优区间进行操作,直到该元素满足条件。

维护区间加的过程考虑差分。我们将对于数组 a a a 的区间加转化为 对于数组 b b b 的区间减,当 b i ≤ 0 b_i\le 0 bi0 时满足题意,其中 1 ≤ i ≤ n 1\le i\le n 1in

因为前后元素增加的量差总为 1 1 1,所以我们考虑将修改量数组进行二次差分:

  • 假设当前增加量数组为
    0 , 0 , 0 , 1 , 2 , 3 , 4 , 5 , 6 , 0 , 0 , 0 0,0,0,1,2,3,4,5,6,0,0,0 0,0,0,1,2,3,4,5,6,0,0,0

  • 对原数组进行第一次差分,得到
    0 , 0 , 0 , 1 , 1 , 1 , 1 , 1 , 1 , − 6 , 0 , 0 0,0,0,1,1,1,1,1,1,-6,0,0 0,0,0,1,1,1,1,1,1,6,0,0

  • 再进行一次差分,得到
    0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 , 0 , − 7 , 6 , 0 0,0,0,1,0,0,0,0,0,-7,6,0 0,0,0,1,0,0,0,0,0,7,6,0

  • 因为我们是从后往前做的, − 7 -7 7 后的 6 6 6 不会对当前和还未被扫描的元素产生影响,所以我们不用太在意这个 6 6 6

  • 此时发现我们只需要修改两处二次差分后的增加量数组,就可以实现区间加公差为一的等差序列。

注意到我们是从后往前进行操作,所以我们可以动态的计算当前元素增加的量。

时间复杂度 O ( n ) O(n) O(n)。具体实现可以看代码。

Code

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int maxn = 3e5 + 5;
int n, k, a[maxn], c[maxn]; // c数组用于记录操作的次数
int now, sum; // now表示当前操作的次数,sum表示未执行操作的总次数(也就是对当前元素产生的影响大小)
int ans; // ans表示最终的答案
signed main(){
    scanf("%lld%lld",&n,&k);
    for(int i = 1; i <= n; i++) scanf("%lld",&a[i]);
    for(int i = n; i >= 1; i --){ // 从后往前扫
        int pos = max(i - k + 1ll, 1ll); // 操作区间的左端点
        int len = i - pos + 1; // 操作区间的长度
        a[i] -= sum;
        int tot = (a[i] + len - 1) / len; // 还需要的操作次数
        if(tot > 0){ // 如果仍需要操作
            ans += tot; 
            sum += len * tot; // 更新操作的总次数
            now += tot; // 更新当前操作的次数
            c[pos] += tot; // 记录操作次数到c数组中
        }
        sum -= now; // 减去已经执行的操作的总次数
        now -= c[i]; // 减去当前操作的次数
    }
    printf("%lld", ans); 
}
  • Codeforces 测评结果:
  • 17
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值