z 老师有一个键盘,里面只有 \(1,0\) 和退格键。键 \(0\) 可以打出一个 0
字符,键 \(1\) 同理,退格键可以删除前面打出的那个字符。
z 老师可以操作这个键盘 \(m\) 次 \((m\le5000)\),求操作完成后打出来的 01 串恰好是 \(S\) 的方案数。
有一件不那么显然的事情:这个 01 串只有长度对答案有影响。同样长度为 5,01001
和 10111
没有任何区别。
为什么?我们经验主义地设计 dp,设 \(f_{i,j}\) 表示操作了 \(i\) 次键盘,使得前 \(j\) 位 01 串被打出的方案数,转移方程就是 \(f_{i,j}=f_{i-1,j-1}+f_{i-1,j+1}\times 2\)。前者是打出 \(s_j\),是 \(0\) 是 \(1\) 打就完了,贡献相同;后者是删除一位,要么是 \(0\) 要么是 \(1\),两个都得删,乘 \(2\)。01 串本身是什么样子?不知道。
不管是先猜出结论,还是先设计 dp 方程,走到这一步就差不多结束了。注意几个细节:
- 边界 \(f_{0,0} = f_{1,0} = f_{1,1} = 1\)。
- \(j\) 从 \(1\) 开始枚举,减 \(1\) 可能有负下标,记得 \(\max\) 上 \(0\)。
这个方法不用计算 \(2^n\),也就不用依赖乘法逆元,可能省了点事?
如果非要当字符串题做,那可不只是麻烦了一点,AC 自动机上跑 dp 吗?
下面是 AC 代码:
#include<bits/stdc++.h>
using namespace std;
const int P=1e9+7;
char s[5005];
long long f[5005][5005];
int main(){
int n,m; scanf("%d%s",&m,s+1);
n=strlen(s+1);
f[0][0]=f[1][0]=f[1][1]=1;
for(int i=2;i<=m;++i) for(int j=0;j<=i;++j)
f[i][j]=(f[i-1][max(0,j-1)]+(f[i-1][j+1]<<1))%P;
printf("%lld\n",f[m][n]);
return 0;
}