[ARC059D] バイナリハック(动态规划)

z 老师有一个键盘,里面只有 \(1,0\) 和退格键。键 \(0\) 可以打出一个 0 字符,键 \(1\) 同理,退格键可以删除前面打出的那个字符。

z 老师可以操作这个键盘 \(m\)\((m\le5000)\),求操作完成后打出来的 01 串恰好是 \(S\) 的方案数。

有一件不那么显然的事情:这个 01 串只有长度对答案有影响。同样长度为 5,0100110111 没有任何区别。

为什么?我们经验主义地设计 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;
}

THE END

posted @ 2021-08-25 21:43  q0000000  阅读(51)  评论(0编辑  收藏  举报