思路:首先可以将题目进行转化:
对于每一个位置i,求出其左区间0比1多的最远端点,和其右区间1比0的最远端点。
而如何刻画0和1的数量关系呢?
这是一个经典的转化思想,将0用-1替代,维护前缀和,若区间的前缀和>0,则说明该区间1比0多,<0的话0比1多
考虑DP:
D
P
[
i
]
DP[i]
DP[i] : 以i结尾,前缀和<0的最长长度
首先考虑左区间的情况,若从1–>i的前缀和<0,则: D P [ i ] = i DP[i] = i DP[i]=i (即最远端点就是起点)
若前缀和
S
u
m
>
=
0
Sum >= 0
Sum>=0 由前缀和的性质:
S
u
m
[
b
]
−
S
u
m
[
a
−
1
]
Sum[b] - Sum[a-1]
Sum[b]−Sum[a−1]可以代表区间
[
a
,
b
]
[a,b]
[a,b] 的和
故我们找到
i
i
i 之前
S
u
m
+
1
Sum+1
Sum+1 第一次出现的位置
j
j
j ,此时
[
j
+
1
,
i
]
[j+1,i]
[j+1,i] 一定是
<
0
<0
<0 的,因为
S
u
m
Sum
Sum 最大只有$1e6 $ 故可以Hash一下,记录每个大于0的值第一次出现的位置。
因为每次前缀和只能加减1 故前缀和的值一定是连续的,故如果存在前缀和 S u m + 2 Sum+2 Sum+2的区间,那么 S u m + 1 Sum + 1 Sum+1 是一定存在的,且一定在其左边更优的位置,故当前缀和大于0时,找 S u m + 1 Sum+1 Sum+1一定是最优解
右区间的解法类似左区间,换一下遍历方向和维护前缀和大于0即可。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
typedef long long ll;
const int A = 1e6 + 10;
char s[A];
int a[A],dp_1[A],dp_2[A],Hash[A];
// Hash[i] : 前缀和i第一次出现的位置
// dp_1[i] : 以i结尾,前缀和<0的最长长度
// dp_2[i] : 以i开头,后缀和>0的最长长度
// 故 Ans = max{dp_1[i] + dp_2[i+1]} (1<=i<len)
int main(){
int sum;
scanf("%s",s);
int len = strlen(s);
for(int i=0 ;i<len ;i++){
if(s[i] == '0') a[i+1] = -1;
else a[i+1] = 1;
}
memset(Hash,-1,sizeof(Hash));
sum = 0;
for(int i=1 ;i<=len ;i++){
sum += a[i];
if(sum < 0) dp_1[i] = i;
else{
if(Hash[sum+1] != -1){
dp_1[i] = i - Hash[sum+1];
}
else dp_1[i] = 0;
if(Hash[sum] == -1) Hash[sum] = i;
}
}
memset(Hash,-1,sizeof(Hash));
sum = 0;
for(int i=len ;i>=1 ;i--){
sum += a[i];
if(sum>0) dp_2[i] = len - i + 1;
else{
if(Hash[-sum+1] != -1){
dp_2[i] = Hash[-sum+1] - i;
}
else dp_2[i] = 0;
if(Hash[-sum] == -1) Hash[-sum] = i;
}
}
int ans = 0;
for(int i=1 ;i<len ;i++){
//printf("%d : dp_1 = %d dp_2 = %d\n",i,dp_1[i],dp_2[i]);
if(dp_1[i] && dp_2[i+1]) ans = max(ans,dp_1[i] + dp_2[i+1]);
}
printf("%d\n",ans);
return 0;
}