CF1367C - Social Distance(构造+贪心+数学规律+普及级)
CF1367C - Social Distance(源地址自⇔CF1367C)
Problem
Example
6
6 1
100010
6 2
000000
5 1
10101
3 1
001
2 2
00
1 1
0
1
2
0
1
1
1
tag:
⇔构造性算法、⇔贪心、⇔数学规律、⇔普及级(*1300)
题意:
对于给定的二进制字符串,规定任意两个 \(1\) 之间至少需要间隔 \(k\) 个 \(0\) ,询问最多可以将多少个 \(0\) 变成 \(1\) 。
思路1:
将字符串分为三段分别谈论:
-
前缀0,其特点是结尾是1,最优的构造方式是
[1+k个0] , [1+k个0] , [……] , [1+k个0] , 【1 …… …… ……】
; -
后缀0,最优的构造方式是
【…… …… …… 1】 , [k个0+1] , [k个0+1] , [……] , [k个0+1]
; -
中间部分,其特点是两端是1,故需要额外垫 \(k\) 个 \(0\) ,最优的构造方式是
【…… …… …… 1】 , [k个0+1] , [k个0+1] , [……] , [k个0+1] , 【垫的k个0】 , 【1 …… …… ……】
;
要额外注意,对于全 \(0\) 的字符串,人为补上 \(k\) 个 \(0\) ,使得其也满足 [1+k个0] , [1+k个0] , [……] , [1+k个0] , [1] , 【补的k个0】
。
AC代码1:
//A WIDA Project
//Time:15ms,Me:500Kb
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#define LL long long
#define IOS() ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define FORD(i,a,b) for(int i=(a);i>=(b);i--)
LL T,n,k,num,ans,flag1,in,out,flag;
string s;
int main(){
IOS();
cin>>T;
while(T-->0){
ans=0;flag1=0;num=0;in=0;out=0;flag=0;
cin>>n>>k;
cin>>s;
FOR(i,0,n-1){
if(s[i]=='1'){//找到第一个1
flag=1;
in=i;
break;
}
}
FORD(i,n-1,0){
if(s[i]=='1'){//找到倒数第一个1
out=i;
break;
}
}
FOR(i,in,out){//处理中间段
if(s[i]=='1'){
if(num-k>0) ans+=(num-k)/(k+1);
num=0;
}else{
num++;
}
}
if(flag==0 && n>k+1){//单独讨论:全0
ans=(n+k)/(k+1);
}else if(flag==0 && n<=k+1){//单独讨论:全0
ans=1;
}else{//处理前后缀0
ans+=in/(k+1);
ans+=(n-out-1)/(k+1);
}
cout<<ans<<endl;
}
return 0;
}
思路2:
(来自排行榜前几的大佬)tag:⇔STL与容器
第一遍遍历,使用带自动排序的容器(如 \(set\) )记录下所有 \(1\) 的位置。
第二遍遍历,只要遇到 Str[i]=='0'
,则使用 lower_bound
查找 \(i-k\) 之后的第一个 \(1\) 的位置,若这个 \(1\) 位于 \(i+k\) 之后、或之后没有 \(1\) ,则说明 \(i\) 这个点可以置 \(1\) , ans++
,并且记录下这个 \(i\) 的位置。
AC代码2:
//A WIDA Project
//Time:46ms,Me:3600Kb
#include<bits/stdc++.h>
using namespace std;
long long T,n,k,ans;
string str;
int main(){
ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin>>T;
while(T-->0){
ans=0;
set<int> S;
cin>>n>>k;
cin>>str;
for(int i=0;i<n;i++){
if(str[i]=='1') S.emplace(i);//记录1
}
for(int i=0;i<n;i++){
if(str[i]=='0'){
auto it=S.lower_bound(i-k);//查找i-k之后的第一个‘1’
if((*it)>i+k || it==S.end()){//如果这个‘1’在i+k之后,或者没有找到‘1’
S.emplace(i);//置‘1’
ans++;
}
}
}
cout<<ans<<endl;
}
return 0;
}
思路3:
(来自队友)tag:⇔模拟
使用 \(num\) 记录每一段连续的 \(0\) 的长度。
遍历字符串,对于每一位,都判断一次:其前面连续的 \(0\) 的长度(即 \(num\) 的值)是否恰好等于 \(k\) 。
- 如果这一位是 \(0\) ,且前面恰好有 \(k\) 个 \(0\) ,则说明可以将这一位置为 \(1\) ,
ans++
,然后将 \(num\) 清零; - 如果这一位是 \(0\) ,且前面连续的 \(0\) 的长度不满 \(k\) ,说明这一位不能变动,
num++
; - 如果这一位是 \(1\) ,且前面恰好有 \(k\) 个 \(0\) ,则说明到这一位为止都满足条件,直接将 \(num\) 清零;
- 如果这一位是 \(1\) ,且前面连续 \(0\) 的长度不满 \(k\) ,说明这一位前面 \(1\) 的数量出现了问题,导致了 \(0\) 的缺失——最近的那个 \(0\) 置成 \(1\) 导致了这个错误,所以需要将其重新变回 \(0\) ,即
ans--
,并将 \(num\) 清零 ;
AC代码3:
//A WIDA Project
//Time:15ms,Me:500Kb
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize("Ofast,no-stack-protector,unroll-loops,fast-math")
#define LL long long
#define IOS() ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define FOR(i,a,b) for(int i=(a);i<=(b);i++)
#define FORD(i,a,b) for(int i=(a);i>=(b);i--)
LL T,n,k,num,ans;
string s;
int main(){
IOS();
cin>>T;
while(T-->0){
cin>>n>>k;
cin>>s;
num=0;ans=0;num=k;
FOR(i,0,n-1){
if(s[i]=='0'){
if(num==k) ans++,num=0;
else num++;
}else{
if(num<k) ans--;
num=0;
}
}
cout<<ans<<endl;
}
return 0;
}
错误次数:【补题】7次
原因:未将计数器 \(num\) 清零。
原因:全 \(0\) 时的边界情况考虑不周。
原因:贸然使用 \(find\) 函数,没有考虑其会输出 \(-1\) ,直接进入数组导致越界,RE。
原因:未考虑中间段连续 \(0\) 的长度可能小于 \(k\) 的值,造成 \(ans\) 值为负。
原因:直接使用 \(in\) 变量和 \(out\) 变量同时为零来判断所给字符是否全部为 \(0\) ,而实际上应当引入新的 \(flag\) 变量单独判断(Hack:5 2 10000。这组数据会被判定为全 \(0\) )。
文 / WIDA
2021.10.14成文
首发于WIDA个人博客,仅供学习讨论
更新日记:
2021.10.14 成文
2021.10.14 补充了思路3