1. 程式人生 > >AC自動機+狀壓DP

AC自動機+狀壓DP

  HDU 2825

題目大義:

  給定m個長度不超過10的單詞,你需要構造一個長度為n的文字串,文字串包含的給定單詞不小於k個,求方案數。

分析:

  看完題目覺得像是數學題,需要一些容斥啥的算算(其實是瞎想,不知道怎麼實現),最後也只會暴力搜尋判斷這種沒分數的寫法。其實是一道DP題,算是一種利用AC自動機做DP的套路,需要學會。

  定義F【i】【j】【mask】,表示當前文字串長度為i,匹配到了AC自動機上的第j個節點,已包含的單詞集合為mask時的方案數。顯然我們可以從j節點向下更新,繼續列舉一個l字母(0<=l<26)來匹配第i+1個字母,那麼我們就可以向下遞推了,假設當前狀態方案數已知,那麼可以累加到F【i+1】【tr j,l】【mask|sum【tr j,l】】中。sum表示到當前這個節點已經包含的單詞集合,採用二進位制。

  注意:sum陣列在建立fail指標的同時求出。

#include<bits/stdc++.h>
using namespace std;
const int N=500;
const int mod=20090717;
int n,m,limit,tot,tr[N][30],sum[N],fail[N],f[30][150][1500],cnt[1500];
char s[N];
void init(){
    memset(tr,0,sizeof(tr));
    memset(sum,0,sizeof(sum));
    memset(fail,0,sizeof(fail));
    memset(f,
0,sizeof(f)); tot=0; f[0][0][0]=1; } void insert(int pos){ int len=strlen(s+1),now=0; for(int i=1;i<=len;++i){ int ch=s[i]-'a'; if(!tr[now][ch]) tr[now][ch]=++tot; now=tr[now][ch]; }sum[now]=1<<pos; } void build(){ queue<int>q; for(int i=0;i<26
;++i){ if(tr[0][i]) q.push(tr[0][i]),fail[tr[0][i]]=0; } while(q.size()){ int x=q.front();q.pop(); for(int i=0;i<26;++i){ if(tr[x][i]){ int v=tr[x][i]; fail[v]=tr[fail[x]][i]; q.push(v); }else tr[x][i]=tr[fail[x]][i]; sum[tr[x][i]]|=sum[tr[fail[x]][i]]; } } } int count(int x){ int res=0; for(;x;x>>=1){ if(x&1) res++; } return res; } int main(){ for(int i=0;i<1<<10;++i) cnt[i]=count(i); while(~scanf("%d%d%d",&n,&m,&limit)){ if(!(n||m||limit)) break; init(); for(int i=0;i<m;++i) scanf("%s",s+1),insert(i); build(); for(int i=0;i<=n;++i){ for(int j=0;j<=tot;++j){ for(int k=0;k<1<<m;++k){ if(!f[i][j][k]) continue; for(int l=0;l<26;++l){ int v=tr[j][l]; f[i+1][v][k|sum[v]]=(f[i+1][v][k|sum[v]]+f[i][j][k])%mod; } } } } int ans=0; for(int i=0;i<=tot;++i) for(int j=0;j<1<<m;++j) if(cnt[j]>=limit) ans=(ans+f[n][i][j])%mod; printf("%d\n",ans); } return 0; }