1. 程式人生 > 其它 >CF1367C - Social Distance(構造+貪心+數學規律+普及級)

CF1367C - Social Distance(構造+貪心+數學規律+普及級)

2021.10.14補題

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 …… …… ……】

  2. 字尾0,最優的構造方式是 【…… …… …… 1】 , [k個0+1] , [k個0+1] , [……] , [k個0+1]

  3. 中間部分,其特點是兩端是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 成文