VEXOBEN
Vexoben
Aug 14, 2018
It takes 5 minutes to read this article.

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=2553

题解

总算是见识了一下AC自动机上跑DP。

将所有禁忌串加到AC自动机里。需要注意的是,如果一个串是另一串的前缀,较长的串一定不会被加入。

用f[i][j]表示走i步,恰好走到j时答案的期望。

转移很好考虑,难点在于统计答案。

如果ch[i][j]是一个禁忌串的结尾节点,那么将它转移到根节点和一个虚构的节点x。否则将它转移到ch[i][j]。统计答案的时候只需要统计转移矩阵的len次幂上(rt,x)的值就可以了。

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;

int n,len,bet;

struct Matrix{
	int n,m;
	long double a[105][105];

	void Init() {
		n=m=0;
		memset(a,0,sizeof(a));
	}
}E;

Matrix operator * (Matrix x,Matrix y) {
	Matrix z; z.Init();
	z.n=x.n; z.m=y.m;
	for (int i=0;i<=z.n;i++) {
		for (int j=0;j<=z.m;j++) {
			for (int k=0;k<=x.m;k++) {
				z.a[i][j]+=x.a[i][k]*y.a[k][j];
			}
		}
	}
	return z;
}

namespace AC {
	char s[N];
	int rt=0,tot=0;
	int ch[N][26],tag[N],fail[N];

	void insert() {
		scanf("%s",s+1); int l=strlen(s+1);
		int now=rt;
		for (int i=1;i<=l;i++) {
			if (tag[now]) return;
			if (!ch[now][s[i]-'a']) ch[now][s[i]-'a']=++tot;
			now=ch[now][s[i]-'a'];
		}
		tag[now]=1;
	}

	void Getfail() {
		queue<int> Q;
		fail[rt]=rt;
		for (int i=0;i<bet;i++) {
			if (ch[rt][i]) fail[ch[rt][i]]=rt,Q.push(ch[rt][i]);
		}
		while (!Q.empty()) {
			int x=Q.front(); Q.pop();
			for (int i=0;i<bet;i++) {
				if (ch[x][i]) {
					fail[ch[x][i]]=ch[fail[x]][i];
					Q.push(ch[x][i]);
				}
				else ch[x][i]=ch[fail[x]][i];
			}
		}
	}
}
using namespace AC;

int main() {
	scanf("%d%d%d",&n,&len,&bet);
	for (int i=1;i<=n;i++) insert();
	Getfail();
	E.n=E.m=tot;
	E.a[tot][tot]=1.0;
	for (int i=0;i<tot;i++) {
		for (int j=0;j<bet;j++) {
			if (tag[ch[i][j]]) {
				E.a[i][rt]+=1.0/bet;
				E.a[i][tot]+=1.0/bet;
			}
			else E.a[i][ch[i][j]]+=1.0/bet;
		}
	}
	Matrix ans; ans.Init();
	ans.n=ans.m=tot;
	for (int i=0;i<=tot;i++) ans.a[i][i]=1;
	while (len) {
		if (len&1) ans=ans*E;
		E=E*E; len>>=1;
	}
	printf("%.10Lf",ans.a[0][tot]);
	return 0;
}