CSP模拟 取模

涉及知识点:反悔贪心

前言

最近开始写 CSP 模拟的题,实际上考的题一点也不 CSP

题意

有一个长度为 \(n\) 的序列 \(A\)\(0\leq A_i<k\),你可以每次选取一个区间,将区间内所有元素 \(+1\),然后将区间内所有元素对 \(k\) 取模。问最少几次操作可以把序列中所有元素都变为 \(0\)

思路

假设现在有一个数列 \([2,3,1,0,3,2]\)\(k=4\),我们考虑如何将它变为 \(0\)

发现每次操作后都要模 \(k\) 的限制非常烦,因此我们做一个转化,每次操作直接加,而“区间内所有元素等于 \(0\)” 转化为了 “区间内所有元素\(\bmod k=0\) ”。

以上图为例,最优的方案为 \((1,7),(2,6),(3,5),(4,4),(4,4)\),共五步,我们把表示“增加值”的序列 \(B\) 用橙色表示。

如果把整张图“倒过来”,如下图,我们惊喜地发现这是一个经典问题,求多少次区间加 \(1\) 能覆盖代表 \(B\) 的橙色部分。很显然,操作次数即为 \(B\) 的差分数组 \(B^d\) 中的 \(>0\) 的数之和。

并且我们发现 \(B_i=(k-A_i)+ck\),且 \(c\) 的取值只可能为 \(0\)\(1\) 时才优,为什么?因为考虑初始时差分数组 \(B^d\) 中所有元素绝对值一定是 \(<k\) 的,那么 \(B_i\) 加一次 \(k\) 体现在 \(B^d\) 上便为 \(B^d_i+k,B^d_{i+1}-k\),可以发现此时 \(B^d_i\) 必为正数且 \(B^d_{i+1}\) 必为负数,那么再加由于 \(B^d_{i+1}\) 只会越来越负不计入答案,而 \(B^d_i\) 为正且越来越大,\(B^d\) 的正数和只会更大。

因此我们考虑找到最优的给 \(B_i\)\(k\) 的方案,根据差分数组的性质,若 \(B\) 上有连续的一段区间 \([l,r-1]\) 都被加上 \(k\),则反映到 \(B^d\) 上为 \(B^d_l+k,B^d_r-k\),此时 \(B^d_l\) 必为正数,\(B^d_r\) 必为负数,假设原先 \(B^d_r\) 为正数,那么这个操作对答案的贡献为原先的 \(B^d_r\) 减去现在的 \(B^d_l\)。听到这 solution 大概已经浮出水面了:即遍历差分数组 \(B^d\),对于每个大于 \(0\)\(B^d_i\),找它前面最小的 \(B^d_j\) 判断操作后能不能使得答案变得更小,贪心即可。

注意操作后 \(B^d_i\) 将会变成负数,它也可以和后面的其他 \(B^d_t>0\) 进行一次操作。这有点类似反悔贪心的思想:对于 \(j<i<t\)\(j\)\(i\) 操作,\(i\) 再和 \(t\) 操作实际上等价于 \(j\)\(t\) 操作,\(i\) 就被“反悔”掉了。

代码

#include<bits/stdc++.h>
using namespace std;
typedef pair<int,int> pii;
const int MAXN=1e7+5;
int n=0,k,a[MAXN],b[MAXN];
string num;
priority_queue<pii,vector<pii>,greater<pii>>pq;
int main(){
    freopen("modulo.in","r",stdin);
    freopen("modulo.out","w",stdout);
    ios::sync_with_stdio(false);
    cin>>k>>num;
    a[++n]=num[0]-'0';
    for(int i=1;i<num.size();i++){
        if(num[i]!=num[i-1])
            a[++n]=num[i]-'0';
    }
    for(int i=1;i<=n;i++){
        if(a[i]!=0) a[i]=k-a[i];
    }
    for(int i=1;i<=n;i++){
        b[i]=a[i]-a[i-1];
    }
    for(int i=1;i<=n;i++){
        if(b[i]<0) pq.push(make_pair(b[i],i));
        else if(b[i]>0){
            if(pq.empty()) continue;
            int id=pq.top().second;
            if(b[id]+k<b[i]){
                pq.pop();
                b[id]+=k;
                b[i]-=k;
                pq.push(make_pair(b[i],i));
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(b[i]>0) ans+=b[i];
    }
    cout<<ans<<endl;
    return 0;
}

后记

CF有道题有点类似,实际上还要简单一点,CF1852C

posted @ 2024-09-09 21:41  MessageBoxA  阅读(10)  评论(0编辑  收藏  举报