Median Replace

有个奇数长度的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;
}