ARC059 F Unhappy Hacking dp
阿新 • • 發佈:2018-11-23
題意:
你有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;
}