1. 程式人生 > >ARC059 F Unhappy Hacking dp

ARC059 F Unhappy Hacking dp

題目連結

題意:
你有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;
}