AC+dp
bzoj1030 [JSOI2007]文本生成器
题意:给出N个模板串,问生成长为M的不含任意模板串的串的方案数(只有大写字母)
#include <bits/stdc++.h>
#define MOD 10007
using namespace std;
const int mod=10007;
int n,m,tot=0;
int fail[6010],ch[6010][30],f[200][6010];
char w[105];//每次输入的串
bool ed[6010];
int KSM(int a,int p){//快速幂求A的P次方
int t=1;
while (p){
if (p&1)t=(t*a)%mod;
a=(a*a)%mod;
p>>=1;
}
return t%mod;
}
void build(){//先维护ch[][]即字典树
int now=0;//0就是ROOT
int len=strlen(w);//得出当前串长
for (int i=0;i<len;i++){//逐个字符打进去
int x=w[i]-'A';//第X条边
if (!ch[now][x]) ch[now][x]=++tot;//这个儿子是空,记录这个是第TOT个结点
now=ch[now][x];//然后把那个结点编号记为NOW,则打入下一字符就是从当前NOW进行了
}
ed[now]=1;//串输入完后就打上串末标记
}
void make(){//本函数功能是要利用FAIL指针把一些节点编号也打上文末标记,求FAIL指针需要广搜
queue<int> q; //开出Q队列
for (int i=0;i<26;i++) //扫一次根结点的每个儿子
if (ch[0][i]) //这个儿了存在
q.push(ch[0][i]);//就把这个儿子打入队列
while (!q.empty()){ //Q队列非空
int r=q.front(); //读出队首
q.pop(); //然后删去
for (int i=0;i<26;i++){ //扫一次当前结点r的儿子
if (!ch[r][i]){ //这个儿子不存在
ch[r][i]=ch[fail[r]][i];//就把这个儿子更新为其失配指针的该边儿子(那个儿子有两个老豆了)
continue; //fail[r]如果未更新,那就是0,指向根即可
} //如果这个儿子存在了
fail[ch[r][i]]=ch[fail[r]][i];//当前r的i边儿子的失配针 指向 当前r的失配针指向的结点的i边儿子
ed[ch[r][i]]|=ed[fail[ch[r][i]]];//r的i儿失配指向的结点的文末标记 赋给 r的i儿文末标记
q.push(ch[r][i]);//打入r的i儿
}
}
}
void doit(){//DP得出结果,需要先维护ch[][]即字典树及DP时按结点的广搜顺序
int ans,i,j,k;
ans=KSM(26,m);//总数,下面再减去不含模板串的串
f[0][0]=1;
for (i=0;i<m;i++) //构造m长度的字符串
for (j=0;j<=tot;j++) //AC自动机上的点
if (!ed[j]) //不是结束字符
for (k=0;k<26;k++){ //枚举下一个点
int x=ch[j][k]; //节点编号
if (ed[x]) continue;//如果该结点是串末不用往后跑,因为现在求不包括模板串的方案数
f[i+1][x]+=f[i][j];//f[i][j]表示长度为i的字符串与AC自动机上的第j个点匹配的方案数
f[i+1][x]%=mod;//f[i+1][x]表示长度为i+1的字符串与AC自动机上的第x个点匹配的方案数,X是J的某儿子
}
for (i=0;i<=tot;i++) //AC自动机上的点
if (!ed[i]) //非文末结点
ans+=mod-f[m][i],ans%=mod;//方案数就累加,防止负数每次先加MOD再模上MOD
printf("%d",ans);
return;
}
int main(){
memset(ed,0,sizeof(ed));
scanf("%d%d",&n,&m); //N个模板串,生成串长是M
for (int i=1;i<=n;i++){ //逐个串
scanf("%s",&w); //输入串
build(); //建树
}
make();
doit();
return 0;
}
Sample Input
2 2
A
B
Sample Output
100
A自动机C+dp
猜你喜欢
转载自blog.csdn.net/cj1064789374/article/details/85381914
今日推荐
周排行