ARC059 F Unhappy Hacking dp

版权声明:本文为博主原创文章,可以转载但是必须声明版权。 https://blog.csdn.net/forever_shi/article/details/83216451

题目链接

题意:
你有n次操作,每次操作可以在之前得到的串后面加一个0或加一个1或者删除最后一个字符,如果没有字符则删除操作后扔是空串。给你一个01串,问n次操作后有多少种方法能得到这个01串,模1e9+7。n<=5000

题解:
难得自己想出一道还有一定思维含量的计数题。今天状态不错,ACR059的E和F两道计数题竟然都自己想出来了。

这题有个 n &lt; = 400 n&lt;=400 的部分分,然后可以想出一个 n 3 n^3 d p dp ,设 d p [ i ] [ j ] [ k ] dp[i][j][k] 表示前 i i 次操作,当前字符串长度是j,与目标串匹配了k个字符的方案数。感觉应该是可以这么 d p dp ,但是我没试过。但是我感觉优化不了,于是决定换个思路。

我一开始打算用总方案数减去不合法的方案数,但是发现并不会算不合法的方案数。后来转念一想,发现只要字符串长度一定,答案与每一位具体填了什么没有关系,于是我们就可以用一个 n 2 n^2 d p dp 计算 n n 次操作后剩余字符长度与目标串长度 l l 一样的个数,然后除以所有长度是 l l 的串的个数,除的时候用逆元,就能算出最终答案。长度是 l l 的串的个数是 2 n 2^n ,因为每个位置可以填0和1两种字符,有 n n 个位置。然后我们考虑如何 d p dp 。我们发现只需要把刚才的 d p dp 状态的第三维去掉,变成 d p [ i ] [ j ] dp[i][j] 表示前 i i 次操作后字符串长度是 j j 的方案数就可以了。每次枚举转移时是加了一个字符还是删除一个字符,加入的话有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;
}

猜你喜欢

转载自blog.csdn.net/forever_shi/article/details/83216451