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的串是长串 )

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

posted @ 2020-10-14 17:18  explorerxx  阅读(179)  评论(0编辑  收藏  举报