传送门
题意
给出一个字符串 S S S( ∣ S ∣ < = 1 e 5 |S|<=1e5 ∣S∣<=1e5),串中只有’0’,‘1’,'2’三种字符,计算不相交的子序列"2020"的最大数量是多少。
题解
观察"2020"的特点,是左右一样的结构,于是可以分解为两个一样的子问题,即判断能组成多少第一个"20"串和多少第二个"20"串,把它们组合组合就是答案。问题就在于如何判断要组成多少个第一个"20"字串呢?暴力枚举显然不行,这时候就要想到二分,想要用二分首先就要判断是否符合二分单调的性质;想想发现如果能组成 X X X个"2020"子序列,显然也能组成 X − 1 X-1 X−1个"2020"子序列,反之如果不能组成 X X X个"2020"子序列,显然也就不能组合出 X + 1 X+1 X+1个子序列。
于是就符合二分性质,那么就二分第一个"20"字串的数量,也就是二分答案,具体步骤看看代码吧。
AC code
#pragma GCC optimize("O3")
#pragma G++ optimize("O3")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 55;
int n;
char s[MAXN];
int check(int x)
{
int a1 = x,a2 = 0,a3 = 0,a4 = 0;
///a1 表示第一个"20"字符中2还需要多少个,a2则是'0'还需要多少个
///a3 表示第二个"20"字符中2还需要多少个,a4则是'0'还需要多少个
for(int i = 1;i <= n;i ++)
{
if(s[i] == '2')
{
if(a1)a1 --,a2 ++;///来了一个'2',优先把第一个"20"中的'2'达成数量要求;
else if(a3)a3 --,a4 ++;///达成之后就填第二个"20"的'2';
}
else if(s[i] == '0')
{
if(a2)a2 --,a3 ++;///来了一个'0',也是优先把第一个"20"中的'0'达成数量要求;
else if(a4)a4 --;///达成之后就填第二个"20"的'0';
}
}
if(a1 == 0 && a2 == 0 && a3 == 0 && a4 == 0)return 1;///所有数量都达成之后说明当前x符合条件
return 0;///否则就不行
}
int main()
{
while(scanf("%d",&n) + 1)
{
scanf("%s",s + 1);
int l = 0,r = n / 4;///答案边界为[0,n/4],很显然的吧。
while(l < r)
{
int mid = l + r >> 1;
if(check(mid))l = mid + 1;
else r = mid;
}
if(check(l) == 0)l --;///看看当前l符不符合,不符合就答案减一(本蒟蒻二分就喜欢这么写,保险一点)
printf("%d\n",l);
}
}