CSP模拟 取模
涉及知识点:反悔贪心
前言
最近开始写 CSP 模拟的题,实际上考的题一点也不 CSP。
题意
有一个长度为 的序列 ,,你可以每次选取一个区间,将区间内所有元素 ,然后将区间内所有元素对 取模。问最少几次操作可以把序列中所有元素都变为 。
思路
假设现在有一个数列 ,,我们考虑如何将它变为 。
发现每次操作后都要模 的限制非常烦,因此我们做一个转化,每次操作直接加,而“区间内所有元素等于 ” 转化为了 “区间内所有元素 ”。
以上图为例,最优的方案为 ,共五步,我们把表示“增加值”的序列 用橙色表示。
如果把整张图“倒过来”,如下图,我们惊喜地发现这是一个经典问题,求多少次区间加 能覆盖代表 的橙色部分。很显然,操作次数即为 的差分数组 中的 的数之和。
并且我们发现 ,且 的取值只可能为 或 时才优,为什么?因为考虑初始时差分数组 中所有元素绝对值一定是 的,那么 加一次 体现在 上便为 ,可以发现此时 必为正数且 必为负数,那么再加由于 只会越来越负不计入答案,而 为正且越来越大, 的正数和只会更大。
因此我们考虑找到最优的给 加 的方案,根据差分数组的性质,若 上有连续的一段区间 都被加上 ,则反映到 上为 ,此时 必为正数, 必为负数,假设原先 为正数,那么这个操作对答案的贡献为原先的 减去现在的 。听到这 solution 大概已经浮出水面了:即遍历差分数组 ,对于每个大于 的 ,找它前面最小的 判断操作后能不能使得答案变得更小,贪心即可。
注意操作后 将会变成负数,它也可以和后面的其他 进行一次操作。这有点类似反悔贪心的思想:对于 , 和 操作, 再和 操作实际上等价于 和 操作, 就被“反悔”掉了。
代码
#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。
本文作者:MessageBoxA
本文链接:https://www.cnblogs.com/SkyNet-PKN/p/18405404
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步