1. 程式人生 > >208.10.10【JSOI2007】【BZOJ1030】【洛谷P4052】文字生成器(AC自動機)(DP)

208.10.10【JSOI2007】【BZOJ1030】【洛谷P4052】文字生成器(AC自動機)(DP)

洛谷傳送門

解析:

為什麼endend又是關鍵字啊啊啊啊!!!

思路:

不要試圖用容斥原理來做這道題。。。

這就是一個ACAC自動機上的DPDP

首先建出ACAC自動機,這時候我們得到所有串之間的包含關係。

然後,如果您有直接統計答案的做法,請私信博主,方便完善題解,這裡提供一種反向統計的方法。

我們不統計滿足條件的方案數,我們統計不滿足的方案數。

因為總方案十分好算,就是26m26^m,所以滿足條件的方案數就是不滿足的補集。

那麼考慮如下DPDPdp[len][k]+=dp[len1][j]dp[len][k]+=dp[len-1][j]

+=dp[len1][j] 其中lenlen是當前考慮的字首字串的長度,jj是所有能夠轉移到kk的自動機結點集合。

這個DPDP十分顯然,我們現在考慮怎麼處理不合法的方案。 考慮我們不能讓任何一個ACAC自動機中已經插入的字串出現在方案中,我們只需要記錄結點是否是某個字串的結尾。每次DPDP之前跳failfail鏈檢驗一下就行了。

程式碼:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define
pc putchar
#define cs const #define end End cs int mod=10007; cs int N=6005; int son[N][26],dp[104][N],fail[N],end[N]; int tot=1,n,m; inline void insert(char c[]){ int len=strlen(c),now=1; for(int re i=0;i<len;++i){ int x=c[i]-'A'; if(!son[now][x])son[now][x]=++tot; now=son[now][x]; } end[
now]=true; } inline void build_ACauto(){ queue<int> q; q.push(1); while(!q.empty()){ int now=q.front(); q.pop(); for(int re i=0;i<26;++i){ if(son[now][i]){ int tmp=fail[now]; while(!son[tmp][i])tmp=fail[tmp]; fail[son[now][i]]=son[tmp][i]; q.push(son[now][i]); } } } } inline int quickpow(int a,int b){ int ans=1; while(b){ if(b&1)ans=ans*a%mod; a=a*a%mod; b>>=1; } return ans; } char c[101]; signed main(){ for(int re i=0;i<26;++i)son[0][i]=1; cin>>n>>m; for(int re i=1;i<=n;++i){ scanf("%s",c); insert(c); } build_ACauto(); dp[0][1]=1; for(int re k=0;k<m;++k){ for(int re j=1;j<=tot;++j){ for(int re i=0;i<26;++i){ int now=j; bool flag=true; while(now){ if(end[son[now][i]]){ flag=false; break; } now=fail[now]; } if(!flag)continue; now=j; while(!son[now][i])now=fail[now]; now=son[now][i]; dp[k+1][now]+=dp[k][j]; dp[k+1][now]%=mod; } } } int ans=0; for(int re i=1;i<=tot;++i)ans=(ans+dp[m][i])%mod; cout<<(quickpow(26,m)-ans+mod)%mod; return 0; }