题目链接
https://codeforces.com/problemset/problem/1367/C
题目描述
给出一个长为 n n n 的 01 01 01 字符串和一个整数 k k k( 1 ≤ k ≤ n ≤ 2 × 1 0 5 1\le k\le n\le 2\times10^5 1≤k≤n≤2×105),要求将串中的若干个 0 0 0 变成 1 1 1,并且要使得变化后的串中任意相邻的两个 1 1 1 之间至少有 k k k 个 0 0 0,问最多可以将多少个 0 0 0 变成 1 1 1。
解题思路
使用贪心思想,从左到右扫描一遍字符串,必须要保证前k
个字符和后k
个都字符不包含1
,才能将字符0
变成1
。
如果直接暴力枚举的话,肯定会超时哒。
如何记录后k
个都字符是否包含1
?
我们可以使用一个数组来记录从右到左最近一个字符1
出现的位置。
设 r i r_i ri为第 i − n i-n i−n位最近出现字符
1
的位置。
例:假如有字符串01001,数组 r r r的值为{2,2,5,5,5}
如何记录前k
个字符是否包含1
?
在从左到右遍历的过程中,使用一个变量loc
来保存最近的1
的下标即可。
当然,还需要处理边界为0的情况,解决的方法也很简单,就是增加两个“哨兵”:对于第1位为0的情况,loc的初始值设为无穷小;对于第n位为0的情况, r n + 1 r_{n+1} rn+1的值设为无穷大。
使用“哨兵”的好处是减少了很多特判,使程序的逻辑保持连贯。
参考代码
#include <iostream>
using namespace std;
const int MAXN=200004;
int r[MAXN];
char str[MAXN];
int main(){
int t,n,k;
cin>>t;
while( t-- ) {
int loc = -MAXN*2,ans=0;
cin>>n>>k;
cin>>str+1;
r[ n+1 ] = MAXN*2;
for(int i = n; i >= 1; i--)
if( str[i] =='1' ) r[i] = i;
else r[i] = r[i+1];
for(int i=1; i <= n; i++) {
if ( str[i] == '0' && i-loc >= k+1 && r[i]-i >= k+1) {
loc = i;
ans++;
}
if ( str[i] == '1' )
loc = i;
}
cout<<ans<<endl;
}
return 0;
}