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\) 。
思路:
將字串分為三段分別談論:
-
字首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】
。
不同的思路:
(來自排行榜前幾的大佬)tag:⇔STL與容器
第一遍遍歷,使用帶自動排序的容器(如 \(set\) )記錄下所有 \(1\) 的位置。
第二遍遍歷,只要遇到 Str[i]=='0'
,則使用 lower_bound
查詢 \(i-k\) 之後的第一個 \(1\) 的位置,若這個 \(1\) 位於 \(i+k\) 之後、或之後沒有 \(1\) ,則說明 \(i\) 這個點可以置 \(1\) , ans++
,並且記錄下這個 \(i\) 的位置。
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; }
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;
}
錯誤次數:【補題】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 成文