题目链接
题目
背单词,始终是复习英语的重要环节。在荒废了3年大学生涯后,Lele也终于要开始背单词了。
一天,Lele在某本单词书上看到了一个根据词根来背单词的方法。比如”ab”,放在单词前一般表示”相反,变坏,离去”等。
于是Lele想,如果背了N个词根,那这些词根到底会不会在单词里出现呢。更确切的描述是:长度不超过L,只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个呢?这里就不考虑单词是否有实际意义。
比如一共有2个词根 aa 和 ab ,则可能存在104个长度不超过3的单词,分别为
(2个) aa,ab,
(26个)aaa,aab,aac…aaz,
(26个)aba,abb,abc…abz,
(25个)baa,caa,daa…zaa,
(25个)bab,cab,dab…zab。
这个只是很小的情况。而对于其他复杂点的情况,Lele实在是数不出来了,现在就请你帮帮他。
Input
本题目包含多组数据,请处理到文件结束。
每组数据占两行。
第一行有两个正整数N和L。(0
分析
总字符串个数为26+26^2+26^3+…+26^n.
包含词根的字符串个数为A+A^2+A^3+…A^n所得到的矩阵的第一行的值的和。A为根据AC自动机确定的状态转移图对应的矩阵。
这题就是POJ2778的变形。
DNA Sequence POJ - 2778
(求不包含特定字符串的长度为N的字符串总数)
变形的部分就是POJ3233
(给定矩阵A,求A+A^2+A^3+…+A^n的结果)
大致的解题思路就是
A+A^2+A^3+…+A^6=A+A^2+A^3+A^3(A+A^2+A^3).
递归求(A+A^2+A^3)并注意讨论下n的奇偶性即可。
AC代码
//187ms 13.1MB
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
#include <queue>
typedef unsigned long long ll;
using namespace std;
const int maxn=50; //单词长度*单词数,结点总数
const int maxc=26; //树分支数
//在trie上添加后缀结点
//后缀结点:x的后缀结点为非x结点y,
//y的路径字符串为x的路径字符串的最长后缀且y的路径字符串是单词
struct ac_au
{
int child[maxn][maxc];//trie
int fail[maxn];//后缀结点
int sta[maxn];//是否为含词根字符串的尾结点
int cnt;//结点的个数
int root; //根结点
int newnode()
{
for(int i=0;i<maxc;i++)
child[cnt][i]=-1;
sta[cnt++]=0;
return cnt-1;
}
void init()
{
cnt=0;
root=newnode();
}
int getid(char ch)
{
return ch-'a';
}
void insert(char *s)//trie构建
{
int len=strlen(s),p=0;
for(int i=0;i<len;i++)
{
int c=getid(s[i]);//第c个儿子
int &num=child[p][c];
if(num==-1)
num=newnode();
p=num;
}
sta[p]=1;//是词根字符串的尾结点
}
void build()//后缀结点的添加
{
int p=0;
queue<int> que;
for(int i=0;i<maxc;i++)
{
int &num=child[p][i];
if(num==-1)
num=p;//为下文铺垫,和树意义无关
else
{
fail[num]=p;//有点多余,初始化都是0,理解算法过程有用
que.push(num);
}
}
while(!que.empty())
{
p=que.front();que.pop();
int x=fail[p];//p的后缀结点为x
//后缀结点的确定是通过bfs,由浅层到深层,故每一个在队列中的结点都确定了后缀结点
if(sta[x]) sta[p]=1;//其后缀结点为含病毒字符串尾结点,
//说明p也是含词根字符串尾结点,可能不是词根,但一定包含词根
for(int i=0;i<maxc;i++)
{
int &num=child[p][i];//引用,改变num就改变child[p][i]
if(num==-1)
num=child[fail[p]][i];
else
{
fail[num]=child[fail[p]][i];
que.push(num);
}
}
}
}
}ac;
struct matrix
{
ll m[50][50];//相当于模2^64
matrix()
{
for(int i=0;i<ac.cnt;i++)
for(int j=0;j<ac.cnt;j++)
m[i][j]=0;
}
};
matrix mul(matrix A,matrix B)//矩阵乘法
{
matrix C;
for(int i=0; i<ac.cnt; i++)
for(int j=0; j<ac.cnt; j++)
{
C.m[i][j]=0;
for(int k=0; k<ac.cnt; k++)
{
ll tmp=A.m[i][k]*B.m[k][j];
C.m[i][j]=(C.m[i][j]+tmp);
}
}
return C;
}
matrix pow(matrix A,ll k)//矩阵快速幂
{
matrix ans=A;
matrix x=A;
k--;
while(k)
{
if(k&1)
ans=mul(ans,x);
k>>=1;
x=mul(x,x);
}
return ans;
}
matrix getMatrix() //从AC自动机得到状态转移图对应的矩阵
{
matrix A=matrix();
for(int i=0; i<ac.cnt; i++)
for(int j=0; j<maxc; j++)
{
//如果从u结点走到v结点不会使得字符串变成含词根字符串,则合法,可以连边
int u=i;
int v=ac.child[u][j];
if(!ac.sta[v])
A.m[u][v]++;
}
return A;
}
matrix add(matrix A,matrix B)//矩阵加法
{
matrix ans;
for(int i=0;i<ac.cnt;i++)
for(int j=0;j<ac.cnt;j++)
ans.m[i][j]=A.m[i][j]+B.m[i][j];
return ans;
}
matrix sum(matrix A,int k)//求A+A^2+A^3+...+A^k
{
matrix ans,x,y;
if(k==1) return A;
x=sum(A,k>>1);
if(k&1)//另一种递归形式:将奇数看作偶数+A^k会超内存
{
y=pow(A,k/2+1);
ans=add(x,y);
ans=add(ans,mul(x,y));
}
else
{
y=pow(A,k>>1);
ans=add(x,mul(x,y));
}
return ans;
}
ll _pow(int k)//快速幂
{
ll ans=1,x=26;
while(k)
{
if(k&1) ans=ans*x;
k>>=1;
x=x*x;
}
return ans;
}
ll all(ll k) //求26+26^2+26^3+...+26^k
{
if(k==1)
return 26;
ll ans,x,y;
x=all(k>>1);
if(k&1)
{
y=_pow(k/2+1);
ans=x+y+x*y;
}
else
{
y=_pow(k/2);
ans=x+x*y;
}
return ans;
}
char s[105];
int main()
{
int n,k;
while(~scanf("%d%d",&n,&k))
{
ac.init();
for(int i=1; i<=n; i++)
{
scanf("%s",s);
ac.insert(s);
}
ac.build();
matrix A=getMatrix();
matrix ans=sum(A,k);
ll res=0;
ll tmp=all(k);
for(int i=0;i<ac.cnt;i++)
res=(res+ans.m[0][i]);
res=tmp-res;
printf("%I64u\n",res);//unsigned long long的输出格式为%I64u
}
return 0;
}