1. 程式人生 > 實用技巧 >Education codeforces Round 96 D題 -string deletion (搬運自自己的洛谷部落格)

Education codeforces Round 96 D題 -string deletion (搬運自自己的洛谷部落格)

本題洛谷有搬運,題號CF1430D

題意概括


------------
給一個0/1串,每次選擇刪除一個字元,然後將整個串前面的一個連續0串或者連續1串(0串在前就刪0串,1串在前就刪1串)給刪除,這兩個刪除算一次操作,求刪空字元的時候最大運算元量是多少

------------
分析



先擺樣例方便下面的說明
下文中敘述時均定字串的最左端是第一位,不按照字串實際下標表示,即本文字串的第一位是1。

eg1: 10010011

求最大運算元量,所以每次儘量刪除更少的字元

對於一個串比如[eg1],如果刪除了某個單字元(eg1的第4位),當它在中間的時候,刪掉它有可能會把左右兩個串給合併,使得它們可以被一次性刪除(刪它產生1000011,消除了字首1就是000011,然後下一次必定會把這一長串0給刪掉),對以後造成不利影響。

模擬一下: 10010011->000011->1(選某個1)或11(選某個0)->空 ---共3次

當這個單字元是字首的時候(比如eg1的第1位,刪了它變成0010011,接著字首消除變成10011,消了3位),如果刪掉它,有可能它緊鄰的後面存在一個連續串,那麼刪掉它就會有一個連續串直接被刪除,一次性刪掉了很多字元。

模擬一下:10010011->10011->11->空 ----共3次

綜上我們不該直接刪除單字元。那麼先刪除多字元中的字元呢?

eg2 0001010

對於字首是多字元(eg2的前三位),只選擇它自己中間的任意字元顯然最優,這樣它只會刪掉自己,而不會多刪任何一個字元。如果選擇了它之外的字元,顯然不但刪除了它,還在它之外又多刪除了一個,使得答案更劣。

eg3 10010011(其實還是eg1)

對於字首是單字元(eg3的第一位),優先選擇利用後面的長串(長串即長度大於等於2的串)刪一個字元然後讓這個單字元作為字首被刪掉;

模擬過程: 10010011->010011->1011->01->空 . . .一共刪了4次

這樣每次字首為單字元的時候只刪掉了兩個字元,顯然最優,否則就是我們在eg1中提到過的。

當然,只剩下單字元的時候,只能一次刪除兩個了,刪到最後就好。沒的玩。

具體實現


我們的貪心只和塊的長度有關,所以我們把字串化作塊集,用陣列儲存每個塊的長度,然後按照上述貪心,如果字首是單字元,用一個指標q(下標)來往後尋找大於2的塊,沒有的話就全都是單字元,一次減2長度來幹掉他們並累計答案。如果字首是多字元,更好辦,直接刪掉這個塊,累計一次答案。

程式碼如下


 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int t;
 4 char s[200100];
 5 int bl[200100],blo;//第i塊裡有多少個數,有多少塊
 6 int main()
 7 {
 8 cin>>t;
 9 for(register int j=1;j<=t;j++)
10 {
11 memset(bl,0,sizeof(bl));blo=0;
12 int n;int q=1;int ans=0;
13 scanf("%d",&n);scanf("%s",s);
14 for(register int i=0;i<n;i++)
15 {
16 blo++;
17 while(1)
18 {
19 bl[blo]++;
20 if(s[i+1]!=s[i]) 
21 break;
22 i++;
23 }
24 }
25 int flag=0;
26 for(int i=1;i<=blo;i++)//現在已經可以用塊來處理這件事情了 
27 {
28 if(flag)//只剩單塊,每次兩個 
29 {
30 ans++;i++;
31 continue;
32 }
33 if(bl[i]>=2)//對於一個大於等於2的聯通塊,刪除自己 
34 {
35 ans++;
36 }
37 else
38 {
39 while(q<i||(q<=blo&&bl[q]<2))q++;//後置指標如果沒有跟上列舉讓它先跟上列舉,然後往後面找大於等於2的塊
40 if(q>blo)//如果q直接飛了出去,說明後面沒有大於2的塊了,直接跳過字首的兩個單字元塊 
41 {
42 flag=1;
43 ans++;
44 i++;
45 }
46 else{//如果存在大於等於2的塊,這個塊少1個 
47 bl[q]--;
48 ans++;
49 }
50 }
51 }
52 printf("%d\n",ans);
53 }
54 return 0;
55 }
56 /*
57 1
58 6
59 100100
60 
61 */
View Code




ps:
**eg4 101100**

( 其實長串的定義也可以是長度大於2的串,比如說eg4,如果定義長串是大於2的串,現在沒有大於2的串了,我們只好一個個(兩個兩個)刪,顯然可以刪三次

101100->1100->00->空

如果說我們定義長串是大於等於2的話,會先選擇第三位的1,然後刪字首1,得到0100,然後再刪第三位的0,接著字首0沒了,剩下10,依然是一步刪除了。

101100->0100->10->空

從上邊我們發現,不管我們定義長串是大於還是大於等於2,刪到它不是長串的時候(各自定義下的長串),再刪它的時候(按照每次只看字首再選擇刪除的貪心原則)最多也最少減掉兩個字元,真的定義大於2的塊是長串的話,手動模擬是沒有問題的,只是如果定義大於2的塊是長串的話,我的程式會認為有兩個字元的塊為一個字元,一次跳過兩個塊的時候其實刪去三個字元,會使答案變少,所以在我的操作中,大於等於2的串是長串 )

------------