版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/83216451
题意:
你有n次操作,每次操作可以在之前得到的串后面加一个0或加一个1或者删除最后一个字符,如果没有字符则删除操作后扔是空串。给你一个01串,问n次操作后有多少种方法能得到这个01串,模1e9+7。n<=5000
题解:
难得自己想出一道还有一定思维含量的计数题。今天状态不错,ACR059的E和F两道计数题竟然都自己想出来了。
这题有个 的部分分,然后可以想出一个 的 ,设 表示前 次操作,当前字符串长度是j,与目标串匹配了k个字符的方案数。感觉应该是可以这么 ,但是我没试过。但是我感觉优化不了,于是决定换个思路。
我一开始打算用总方案数减去不合法的方案数,但是发现并不会算不合法的方案数。后来转念一想,发现只要字符串长度一定,答案与每一位具体填了什么没有关系,于是我们就可以用一个 的 计算 次操作后剩余字符长度与目标串长度 一样的个数,然后除以所有长度是 的串的个数,除的时候用逆元,就能算出最终答案。长度是 的串的个数是 ,因为每个位置可以填0和1两种字符,有 个位置。然后我们考虑如何 。我们发现只需要把刚才的 状态的第三维去掉,变成 表示前 次操作后字符串长度是 的方案数就可以了。每次枚举转移时是加了一个字符还是删除一个字符,加入的话有0和1两种情况,删除的话要特判一下当前是否是空串。转移方程不难想。
代码:
#include <bits/stdc++.h>
using namespace std;
int n,l;
char s[5010];
long long dp[5010][5010];
const long long mod=1e9+7;
inline long long ksm(long long x,long long y,long long mod)
{
long long res=1;
while(y)
{
if(y&1)
res=res*x%mod;
x=x*x%mod;
y>>=1;
}
return res;
}
int main()
{
scanf("%d",&n);
scanf("%s",s+1);
l=strlen(s+1);
dp[0][0]=1;
for(int i=1;i<=n;++i)
{
for(int j=0;j<=i;++j)
{
dp[i][j]=(dp[i][j]+dp[i-1][j-1]*2)%mod;
if(j==0)
dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
else
dp[i][j-1]=(dp[i][j-1]+dp[i-1][j])%mod;
}
}
long long ans=ksm(2,l,mod);
ans=ksm(ans,mod-2,mod);
ans=ans*dp[n][l]%mod;
printf("%lld\n",ans);
return 0;
}