题目
思路
首先考虑没有 ?
的时候怎么做。
注意到串中只剩 1
的时候,总是可以完成任务;也就是说,1
太多没关系,但有 0
就不行。所以我们会想到先去删除零。
接下来观察操作的性质:当 011
或 001
操作时,都是 0
和 1
各减少一个;换句话说,相邻的 01
可以直接相互湮灭。但 000
操作可以使 0
直接减少
2
2
2 个,所以我们优先进行 000
型操作。
那么,进行第一轮简单化简后,就得到 1
和长度为
1
1
1 或
2
2
2 的 0
交错出现的串。接下来是否还有可能进行 000
操作呢?有。00100
型,可以通过 1
的湮灭使得 000
再次出现。之后就判断 0
和 1
数量多少就行。
但是这样就无法推广到 ?
存在的情况了——我们希望的是 线性扫描,即从左往右扫描即可判断答案,这样就可以逐个确定 ?
然后计数了。考虑能否线性扫描?
要找到方案的共性:无论是不是 00100
型,1
总是要立即与其左侧的 0
相互湮灭。因为 1
长的时候,与 0
湮灭等价于比较数量;1
短的时候,通过湮灭使得 0
相连。
于是可以维护一个形如 111...100
的串,末尾 0
最多
2
2
2 个。遇到 1
就与左侧 0
相互湮灭。遇到 0
就往后叠加,达到
3
3
3 个 0
就变为
1
1
1 个。线性扫描可行。
然而状态数是
O
(
3
n
)
\mathcal O(3n)
O(3n),完全不行。我们还需要注意到一个惊人的事实:最左侧的 1
数量只会增加。因为要么湮灭一个 0
,要么增加一个 1
,从来不会消耗 1
。而最后我们只需要判断
c
n
t
1
>
c
n
t
0
cnt_1>cnt_0
cnt1>cnt0,所以只需要保留
3
3
3 个 1
即可。当然也可以更细致一点,由于
2
∤
n
2\nmid n
2∤n,所以
c
n
t
1
≠
c
n
t
0
cnt_1\ne cnt_0
cnt1=cnt0,于是也可以改为判断
c
n
t
1
⩾
c
n
t
0
cnt_1\geqslant cnt_0
cnt1⩾cnt0,只保留
2
2
2 个 1
就够了。
时间复杂度 O ( 3 2 n ) \mathcal O(3^2n) O(32n) 。
代码
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
int a = 0, c = getchar(), f = 1;
for(; !isdigit(c); c=getchar())
if(c == '-') f = -f;
for(; isdigit(c); c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
const int MOD = 1e9+7;
inline void modAddUp(int &x,const int y){
if((x += y) >= MOD) x -= MOD;
}
char str[300005];
int dp[2][3][3];
int main(){
scanf("%s",str+1);
int n = int(strlen(str+1));
dp[0][0][0] = 1;
for(int i=1,fr=0; i<=n; ++i,fr^=1){
memset(dp[i&1],0,3*3<<2);
rep(one,0,2) rep(zero,0,2){
const int &w = dp[fr][one][zero];
if(str[i] != '1') modAddUp(dp[i&1][one][(zero&1)+1],w);
if(str[i] != '0'){ // choose 1
if(zero) modAddUp(dp[i&1][one][zero-1],w);
else modAddUp(dp[i&1][one+1-(one>>1)][zero],w);
}
}
}
int ans = 0;
rep(one,0,2) rep(zero,0,one)
modAddUp(ans,dp[n&1][one][zero]);
printf("%d\n",ans);
return 0;
}