题目大义:
给定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; }
原文地址:https://www.cnblogs.com/kgxw0430/p/10255650.html
时间: 2024-10-08 00:34:50