背单词,始终是复习英语的重要环节。在荒废了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<N<6,0<L<2^31)

第二行有N个词根,每个词根仅由小写字母组成,长度不超过5。两个词根中间用一个空格分隔开。

Output对于每组数据,请在一行里输出一共可能的单词数目。

由于结果可能非常巨大,你只需要输出单词总数模2^64的值。

Sample Input

2 3
aa ab
1 2
a

Sample Output

104
52
思路:题目要求不超过L的所有符合单词,我们先考虑等于L
对于所有满足的情况 = 所有情况-不满足情况
不满足情况即是不包含词根的单词,对于计算满足长度所有情况,可以联想到矩阵快速幂,这样我们就只需要建一个矩阵图
对于建图,我们知道,所有以词根作为前缀子串的都是不符合条件的。
从根开始记录每种前缀可以添加的字母路径数(字母不是词根的结尾)。
于是我们可以想到AC自动机,对于自动机补全的trie图,每个单词的fail都指向其最长子后缀
也就是从根以fail指针向下建图的话,当遍历到一个词根末尾时,其下面的有词根都时不合法的,这可以用dfs维护。
我们知道当矩阵中存有边权(代表路径数)时(相当于到每个点走一条路径),我们将自身乘自身,得到的二次幂矩阵,每个位置的权值就相当于从i走到j走两条路径的路径数目。(离散数学)
这样推广到这,就相当于算出矩阵的L次幂,其第一行的和就是从0长度到L长度经过各个路径以各种字母结尾的路径和,这是长度等于L的情况。
那么对于长度<=L的所有情况,我们知道 Σ(A1+A2+A3+...+AL),然后其第一行的和即是答案。
一开始写的是递归,对于L=6为偶数,A1+A2+A3+A3(A1+A2+A3)
         对于L=5为奇数,A1+A2+A2(A1+A2)+A5
但也许是姿势不对,一直tle。 然后看了Discus,我们可以添加一维,(假设原本矩阵为L,此L!=题面的L),使得L+1列全是1,这样对于第L个矩阵maps【1】【L+1】就储存Σ(A1+A2+A3+...+A[L-1])
对于取模,因为是对1<<64取模,太大了直接取不了(睿智的尝试),可以直接把相关数组开成unsign long long 可以直接对溢出舍去相当于取模
还有利用等比数列公式时,由于涉及除法,利用欧拉定理求出逆元即可。 附上丑代码
 #include<bits/stdc++.h>
using namespace std; typedef unsigned long long ull;
struct Node
{
int y,next;
} node[];
int cnt,head[],L;
int n;
ull len;
int tohash[],topos;
void add(int x,int y)
{
node[++cnt].y=y;
node[cnt].next=head[x];
head[x]=cnt;
} struct MAP
{
ull m[][];
friend MAP operator*(const MAP a,const MAP b)
{
MAP tmp;
for(int i=; i<=L+; i++)
{
for(int j=; j<=L+; j++)
{
tmp.m[i][j] = ; for(int k=; k<=L+; k++)
{
tmp.m[i][j] = tmp.m[i][j]+ a.m[i][k]*b.m[k][j];
} }
}
return tmp;
}
// friend MAP operator+(const MAP a,const MAP b)
// {
// MAP tmp;
// for(int i=1;i<=L;i++)
// {
// for(int j=1;j<=L+;j++)
// {
// tmp.m[i][j] = a.m[i][j] + b.m[i][j];
// }
// }
// return tmp;
// }
void init()
{
for(int i=; i<=L+; i++)
{
for(int j=; j<=L+; j++)
{
m[i][j] = ;
}
m[i][i] = ;
}
}
} maps;
struct ACTO
{
int trie[][],tot;
int dp[];
int fail[];
int End[];
void init()
{
tot = cnt = L = topos = ;
memset(trie,,sizeof(trie));
memset(maps.m,,sizeof(maps.m));
memset(head,,sizeof(head));
memset(dp,,sizeof(dp));
memset(fail,,sizeof(fail));
memset(End,,sizeof(End));
memset(tohash,,sizeof(tohash));
}
void Insert(char *str,int num)
{
int len = strlen(str),p=;
for(int i=; i<len; i++)
{
int ch = str[i]-'a';
if(!trie[p][ch])trie[p][ch]=++tot;
p = trie[p][ch];
}
End[p]=num;
}
void get_fail()
{
queue<int>que;
while(!que.empty())que.pop();
for(int i=; i<; i++)if(trie[][i])que.push(trie[][i]);
while(!que.empty())
{
int p = que.front();
que.pop();
for(int i=; i<; i++)
{
if(trie[p][i])
{
fail[trie[p][i]] = trie[fail[p]][i];
que.push(trie[p][i]);
}
else trie[p][i] = trie[fail[p]][i];
}
}
}
void make_edge()
{
for(int i=; i<=tot; i++)
{
add(fail[i],i);
}
}
void dfs(int x,int f)
{
if(End[x])f=;
dp[x] = f;
for(int i=head[x]; i; i=node[i].next)
{
dfs(node[i].y,f);
} }
void graph()
{
for(int i=; i<=tot; i++)
{
if(!dp[i])
{
L++;
if(!tohash[i])tohash[i] = ++topos;
for(int j=; j<; j++)
{
if(!dp[trie[i][j]])
{
if(!tohash[trie[i][j]])tohash[trie[i][j]]=++topos;
maps.m[tohash[i]][tohash[trie[i][j]]]++;
}
}
}
}
}
}; MAP qpow(MAP a,ull b)
{
MAP ans;
ans.init();
MAP base = a;
while(b)
{
if(b&)ans = ans*base;
base = base * base;
b >>= ;
}
return ans;
} ACTO AC; //MAP cal(int l,int r)
//{
//
// if(l==r)return maps;
// int mid = r/2;
// if(r & 1)
// {
// return cal(l,mid)+qpow(maps,mid)*cal(l,mid)+qpow(maps,r);
// }
// else
// {
// return cal(l,mid)+cal(l,mid)*qpow(maps,mid);
// }
//} ull qmulti(ull m,ull n)
{
ull ans = ;
while(n)
{
if(n&)ans += m;
m += m;
n >>= ;
}
return ans;
}
ull qpow(ull a,ull b)
{
ull ans = ;
ull base = a;
while(b)
{
if(b&)ans = qmulti(ans,base);
base = qmulti(base,base);
b >>= ;
}
return ans;
} int main()
{
while(~scanf("%d%llu",&n,&len))
{
AC.init();
memset(maps.m,,sizeof(maps));
char W[];
for(int i=; i<=n; i++)
{
scanf(" %s",W);
AC.Insert(W,i);
}
AC.get_fail();
AC.make_edge();
AC.dfs(,);
AC.graph();
for(int i=;i<=;i++)
{
for(int j=;j<=;j++)
{
printf(" %d ",maps.m[i][j]);
}
puts("");
}
for(int i=;i<=L+;i++)maps.m[i][L+]=;
MAP ans = qpow(maps,len);
ull total = (qpow(,len+)-)*qpow(,(1ull<<)-);
for(int i=;i<=L+;i++)total -= ans.m[][i];
printf("%llu\n",total+);
}
}

HDU 2243考研路茫茫——单词情结 (AC自动机+矩阵快速幂)的更多相关文章

  1. hdu 2243 考研路茫茫——单词情结 ac自动机+矩阵快速幂

    链接:http://acm.hdu.edu.cn/showproblem.php?pid=2243 题意:给定N(1<= N < 6)个长度不超过5的词根,问长度不超过L(L <23 ...

  2. HDU 2243 考研路茫茫——单词情结(AC自动机+DP+快速幂)

    题目链接 错的上头了... 这题是DNA的加强版,26^1 +26^2... - A^1-A^2... 先去学了矩阵的等比数列求和,学的是第二种方法,扩大矩阵的方法.剩下就是各种模板,各种套. #in ...

  3. hdu 2243 考研路茫茫——单词情结 AC自动机 矩阵幂次求和

    题目链接 题意 给定\(N\)个词根,每个长度不超过\(5\). 问长度不超过\(L(L\lt 2^{31})\),只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个? 思路 状态(AC自动 ...

  4. [hdu2243]考研路茫茫——单词情结(AC自动机+矩阵快速幂)

    题意:长度不超过L,只由小写字母组成的,至少包含一个词根的单词,一共可能有多少个. 解题关键:利用补集转化的思想,先求一个词根也不包含的单词个数,然后用总的减去即可.长度不超过L需要用矩阵维数增加一倍 ...

  5. hdu 2243 考研路茫茫——单词情结(AC自动+矩阵)

    考研路茫茫——单词情结 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  6. hdu_2243_考研路茫茫——单词情结(AC自动机+矩阵)

    题目链接:hdu_2243_考研路茫茫——单词情结 题意: 让你求包含这些模式串并且长度不小于L的单词种类 题解: 这题是poj2788的升级版,没做过的强烈建议先做那题. 我们用poj2778的方法 ...

  7. HDU 2243 考研路茫茫——单词情结(AC自动机+矩阵)

    考研路茫茫——单词情结 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total ...

  8. HDU 2243 考研路茫茫——单词情结

    考研路茫茫——单词情结 Time Limit: 1000ms Memory Limit: 32768KB This problem will be judged on HDU. Original ID ...

  9. HDU 2243 考研路茫茫——单词情结 求长度小于等于L的通路总数的方法

    http://acm.hdu.edu.cn/showproblem.php?pid=2243 这是一题AC自动机 + 矩阵快速幂的题目, 首先知道总答案应该是26^1 + 26^2 + 26^3 .. ...

  10. HDU2243 考研路茫茫——单词情结 ——AC自动机、矩阵优化

    题目链接:https://vjudge.net/problem/HDU-2243 考研路茫茫——单词情结 Time Limit: 2000/1000 MS (Java/Others)    Memor ...

随机推荐

  1. AFM论文精读

    深度学习在推荐系统的应用(二)中AFM的简单回顾 AFM模型(Attentional Factorization Machine) 模型原始论文 Attentional Factorization M ...

  2. H5_0004:JS设置循环debugger的方法

    在HTML页面加上如下代码,则PC打开控制台后,就会循环debugger,防止调试代码. <script>eval(function (p, a, c, k, e, r) { e = fu ...

  3. spring-cloud-sleuth 和 分布式链路跟踪系统

    ==================spring-cloud-sleuth==================spring-cloud-sleuth 可以用来增强 log 的跟踪识别能力, 经常在微服 ...

  4. 作业二Wordcount

    1,github地址 https://github.com/dtneverdie/word-count 2,PSP表格 3,解题思路 先从理论上判断应该先将文件内的字符全部读入,然后根据分隔符来进行单 ...

  5. 【vue】函数式组件

    在 2.5.0 及以上版本中,如果你使用了单文件组件,那么基于模板的函数式组件可以这样声明: <template functional> <div class="cell& ...

  6. APK改之理 手游修改改编安卓程序工具安装使用教程

    APK改之理 手游修改改编安卓程序工具安装使用教程 --APK破解付费程序 apk改之理是pc平台上一款非常好用的apk反编译工具,他将反编译以及签名等功能集中在一起,并且拥有非常人性化的操作界面,如 ...

  7. Spring ES

    elasticsearchTemplate 和 ElasticsearchRepository JPA中有个ElasticsearchRepository可以做Elasticsearch的相关增删改查 ...

  8. IMDB-二分类问题

    from keras.datasets import imdb from keras.utils.np_utils import to_categorical import numpy as np f ...

  9. 增加swap分区

    起因:开发人员说tomcat关闭了,然后我排查了下,发现内存耗尽,然后临时用swap分区,以供当前运行的程序使用. 先用free -h查看一下swap的大小 1.添加swap分区 使用dd命令创建/h ...

  10. Kali Linux的vi编辑器/vim编辑器使用方法

    转载声明:本文为转载文章 原文地址:https://www.52host.cn/blog/kali-linux-vi-editor/ Kali Linux系统的vi编辑器/vim编辑器的使用和Cent ...