传送门
先考虑选最多禁忌串的问题
感受一下,如果禁忌串之间没有包含关系,一定是可以从前往后贪心搞的,直接建AC自动机跑匹配,找到一个禁忌串的末尾就回到根上,并把禁忌串数量+1
(所以起初我想的是把包含其他禁忌串的禁忌串去掉再建AC自动机,不过后来发现其实直接跑就可以了,因为顺着跑下去,先到达的末尾节点一定是被包含的禁忌串,包含的禁忌串是跑不到的)
这就是一种经典的AC自动机DP了,每个节点继承fail的状态,然后
f[i][j][k]
表示串长为
i
,跑到AC自动机的第
看到
len≤109
,想想能不能加速转移(矩阵乘法?卷积?推公式?)
可是即使能够快速转移,我们计算的是期望,如果依照这种思路做,最后除以总方案数根本没法算,这怎么办呢?
“长度为len的字符串中包含的禁忌串数量”的期望等价于“长度为len的字符串经过AC自动机末尾节点的次数”的期望
因为末尾节点独立,根据期望的线性性质,它等于“经过各末尾节点次数”的期望之和
这样我们就把问题转移到考虑每个末尾节点的贡献上
……
然后就不会做了:(,并不是很懂期望的那一套理论,只会矩乘求概率爽爽
询问了红太阳mrazer,理解了一下期望的线性性质,得知只要倒着DP就可以了,这样思路就比较明显了,
f[i][j]
表示,当前在
j
节点,走
对于跳回根节点并数量+1的情况,转移矩阵再加一列常数列,如果能跳回根节点并数量+1,就为1,反之为0;初始矩阵的常数列设为1就可以了
时间复杂度
O(|T|3loglen)
总结一下,之前也做过一些期望的题目,但感觉根本连门都没入,像这道题目并没有很高的思维难度,也是一堆知识点的堆砌,最后没有想到倒着DP可以做期望,功亏一篑,很不应该,不过也恰恰反应了自身的薄弱,以后还是要加深思考,多做点综合应用和锻炼思维的题目,少写板子题
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
int len,alp,n,root=1,cnt=1;
int trie[80][26],fail[80];
char s[17];
bool is_end[80];
queue<int>q;
struct matrix
{
int r,c;
long double a[80][80];
void clr(){r=c=0;memset(a,0,sizeof(a));}
}ini,tra;
matrix operator *(matrix A,matrix B)
{
matrix C;
C.clr();
for (int k=1;k<=A.c;++k)
for (int i=1;i<=A.r;++i)
if (A.a[i][k]!=0)
for (int j=1;j<=B.c;++j)
C.a[i][j]+=A.a[i][k]*B.a[k][j];
C.r=A.r;C.c=B.c;
return C;
}
void insert(char *s)
{
int len=strlen(s),now=root;
for (int i=0;i<len;++i)
{
if (!trie[now][s[i]-'a']) trie[now][s[i]-'a']=++cnt;
now=trie[now][s[i]-'a'];
}
is_end[now]=1;
}
void build()
{
int x,tmp;
for (q.push(root);!q.empty();q.pop())
{
x=q.front();
is_end[x]|=is_end[fail[x]];
for (int i=0;i<alp;++i)
if (trie[x][i])
{
tmp=fail[x];
while (tmp&&!trie[tmp][i]) tmp=fail[tmp];
if (tmp&&x!=root) fail[trie[x][i]]=trie[tmp][i];
else fail[trie[x][i]]=root;
q.push(trie[x][i]);
}
}
}
main()
{
scanf("%d%d%d",&n,&len,&alp);
for (int i=1;i<=n;++i)
scanf("%s",s),
insert(s);
build();
ini.r=1;ini.c=cnt+1;
ini.a[1][cnt+1]=1;
tra.r=cnt+1;tra.c=cnt+1;
tra.a[cnt+1][cnt+1]=1;
for (int i=1;i<=cnt;++i)
if (!is_end[i])
for (int tmp,j=0;j<alp;++j)
{
tmp=i;
while (tmp&&!trie[tmp][j]) tmp=fail[tmp];
if (tmp)
{
if (!is_end[trie[tmp][j]]) tra.a[trie[tmp][j]][i]+=1.0/alp;
else tra.a[root][i]+=1.0/alp,tra.a[cnt+1][i]+=1.0/alp;
}
else
tra.a[root][i]+=1.0/alp;
}
for (;len;len>>=1,tra=tra*tra)
if (len&1) ini=ini*tra;
printf("%.10lf",(double)ini.a[1][1]);
}