传送门
题意(转换一下):
给出一个01串,可以最多选择k个l长度的子串,全部变为0或1。求min(size 0,size 1)
(
1
≤
n
,
k
,
l
≤
1
0
6
,
l
≤
n
)
(1 \le n, k, l \le 10^6, l \le n)
(1≤n,k,l≤106,l≤n)
思路:
这道题如果可以选择越多的子串,我们可以虽然可以得到更好的值(即最小化答案),选得越多,根据贪心来看,我们最小化的增速也会越小,也就是说我们k越大,答案虽然在优化,但是优化的增速会越小,也就是说形成了一个凸函数,k是有限制的,这就想到了wqs二分,我们每次选择增加一个代价,也就是让我们原本想要的最小化变大(反着意愿来做),因此,我们二分到一个代价mid,用mid去贪心,如果这个mid恰好,我们恰好到达k,如果这个代价大了,说明我们贪心去搞,不会选择那么多长度为l的子串(因为要使总花费最小),这时候就要将代价调小,否则调大这个代价,调到一个合适的代价,选择的也正好是k个子串了。
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int a[N];
int n,k,l;
#define x first
#define y second
#define pii pair<int,int>
pii p[N];
pii sol(int c){
p[0]={0,0};
for(int i=1;i<=n;i++){
p[i]=p[i-1];
p[i].x=p[i-1].x+a[i];
if(i<=l){
p[i]=min(p[i],{c,1});
}
else{
p[i]=min(p[i],{p[i-l].x+c,p[i-l].y+1});
}
}
return p[n];
}
int erf(){
int l=0,r=n;
while(l<r){ //二分代价
int mid=l+r>>1;
if(sol(mid).y<=k) r=mid;
else l=mid+1;
}
int ans=sol(l).x-k*l;
return ans;
}
int main(){
scanf("%d%d%d",&n,&k,&l);
string s;
cin>>s;
for(int i=0;i<s.size();i++){
if(s[i]>='a'&&s[i]<='z'){
a[i+1]=0;
}
else a[i+1]=1;
}
int k1=erf();
for(int i=1;i<=n;i++){
a[i]=1-a[i];
}
int k2=erf();
int res=min(k1,k2);
printf("%d\n",res);
return 0;
}