BZOJ2553:[BeiJing2011]禁忌 AC自动机+矩阵快速幂

首先我们把这些串扔到AC自动机上。。。

可以发现要匹配尽可能多的子串,我们只要贪心地在AC自动机上匹配即可

所以建完trie树后需要删去是某个模版串后缀的所有模版串

然后我们根据AC自动机上的状态,可以得出从第i位转移到第i+1位时,原状态等价于自动机上第j个状态,现状态等价于自动机上第k个状态的概率的转移方程:

f[i+1][k]=f[i][j]/alphabet

(在这之前先把fail指针的信息整合到trie的子节点关系上即可)

特别地,当新状态为模版串末位时,我们直接转移到trie树的根节点,并累加伤害的期望值

因为状态有限,且只能由第i位转移到第i+1位。。

我们就可以用矩阵快速幂轻松地优化了!(总结点数还是比较和谐的)

AC Code

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#define N 110
#define maxsiz 101
using namespace std;
struct matrix {
	int l,r;
	long double m[N][N];
}ori,tmp,f;
int ch[N][26],n,len,alphabet,fail[N],q[N],m,cnt;
char s[N];
bool boo[N];
void build_trie(int id){
	int now=0;
	for (int i=1;i<=m;i++) {
		if (!ch[now][s[i]-'a']) ch[now][s[i]-'a']=++cnt;
		now=ch[now][s[i]-'a'];
		if (boo[now]) break;
	}
	boo[now]=1;
}
void getfail(){
	int h=0,t=1;q[h]=0;
	while (h!=t) {
		int now=q[h++];if (h>maxsiz)h=0;
		int tmp=0;
		for (int i=0;i<alphabet;i++) if (ch[now][i]){
			int x=ch[now][i];int y=fail[now];
			while (y&&(!ch[y][i])) y=fail[y];
			if (ch[y][i]!=x) fail[x]=ch[y][i];
			else fail[x]=0;
			q[t++]=x;if (t>maxsiz) t=0;
		}
		else ch[now][i]=ch[fail[now]][i];
	}
	ori.m[cnt+1][cnt+1]=1;
}
void mul(matrix &x,matrix &y){
	tmp.l=x.l;tmp.r=y.r;
	memset(tmp.m,0,sizeof(tmp.m));
	for (int k=0;k<x.r;k++)
	for (int i=0;i<x.l;i++)
	for (int j=0;j<y.r;j++)
	tmp.m[i][j]+=x.m[i][k]*y.m[k][j];
	x=tmp;
	return ;
}
void build_matrix(){
	long double xx=(long double)1/alphabet;
	for (int i=0;i<=cnt;i++) if (!boo[i])
	for (int j=0;j<alphabet;j++) 
	if (boo[ch[i][j]]) ori.m[i][cnt+1]+=xx,ori.m[i][0]+=xx;
	else ori.m[i][ch[i][j]]+=xx;
	ori.m[cnt+1][cnt+1]=1;
}
void dfs(int now){
	if (boo[now]) for (int i=0;i<alphabet;i++) ch[now][i]=0;
	else for (int i=0;i<alphabet;i++) if (ch[now][i]) dfs(ch[now][i]);
}
int main(){
	scanf("%d%d%d",&n,&len,&alphabet);
	for (int i=1;i<=n;i++){
		scanf("%s",s+1);
		m=strlen(s+1);
		build_trie(i);
	} 
	dfs(0);
	ori.l=ori.r=cnt+2;
	f.l=1;f.r=cnt+2;
	f.m[0][0]=1;
	getfail();
	build_matrix();
	for (;len;len>>=1,mul(ori,ori)) if (len&1) mul(f,ori);
	double ans=f.m[0][cnt+1];
	printf("%.9lf",ans);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值