有个奇数长度的01串\(s\),其中有若干位置是?。
每次可将\(3\)个连续的字符替换成这三个数的中位数。
求有多少方案将?替换成0/1使得进行\(\frac{N-1}{2}\)次操作后的字符串是1?
\(1\leq |S|\leq 300000\)。
题解
仓鼠《动态规划选讲》。
很多这样的题,套路就是先考虑一个判定的过程,然后把过程写到DP里面去就可以了。所有我们先考虑判定的过程。
维护一个栈,这个栈满足存在一个分界点,前面全是1后面全是0。这里前面后面指的是,把栈从栈底向栈顶写下来。然后从左往右扫。
如果当前是0,就把0压入到栈里面去。如果有连续的三个0,可以把这\(3\)个0消掉\(2\)个变成\(1\)个0。
如果当前是1,当栈顶是一个0的时候显然可以相互抵消(因为再找一个数取中位数,取决于再找的数),否则直接压入栈。
一个显然的事情是,栈里面0的个数不超过\(2\)。同时,当栈里面1的个数达到\(2\)时,那么最终一定可以让字符串变成1。因此,当1的个数超过\(2\)时,可以看成就是\(2\)。
所以栈的种类数只有\(3 × 3 = 9\)种。
最终可以变成1的条件,就是最后的栈里面1个数不少于0个数。
直接把当前栈长成什么样记下来,从左往右DP即可。
时间复杂度是\(O(N)\)。
CO int N=3e5+10;
char str[N];
int F[N][3][3];
int main(){
scanf("%s",str+1);
int n=strlen(str+1);
F[0][0][0]=1;
for(int i=1;i<=n;++i){
for(int j=0;j<=2;++j)for(int k=0;k<=2;++k){
if(str[i]!='0'){
if(k) chkadd(F[i][j][k-1],F[i-1][j][k]);
else chkadd(F[i][min(j+1,2)][k],F[i-1][j][k]);
}
if(str[i]!='1'){
if(k==2) chkadd(F[i][j][1],F[i-1][j][k]);
else chkadd(F[i][j][k+1],F[i-1][j][k]);
}
}
}
int ans=0;
for(int i=0;i<=2;++i)for(int j=0;j<=i;++j)
chkadd(ans,F[n][i][j]);
printf("%d\n",ans);
return 0;
}