bzoj2553: [BeiJing2011]禁忌

传送门:http://www.lydsy.com:808/JudgeOnline/problem.php?id=2553

思路:第一件事当然是建立AC自动机。。。

现在我们建好了AC自动机,那么我们就在AC自动机上走,走到一个终止节点就算我们找到一个禁忌串,然后返回根节点重新匹配。

和bzoj1030类似,考虑DP,设f[i][j]为现在长度为i,走到j号节点的期望。

转移就是枚举下一个字符。下一个字符是终止节点就跳回root下次重新走,ans的期望就可以增加1/字符集大小。


因为长度最大有10^9,显然直接DP会无论空间还是时间都会爆炸。。。

所以用矩阵乘法+快速幂加速转移


现在考虑怎么处理出初始的转移矩阵

先算出a[i][j]表示i一步到j的概率

用bfs就可以实现,如果j是i的儿子,那么a[i][j]+=1/字符集大小

为了方便我们新建一个节点n=cnt(总结点数)+1

每次转移root时也转移到它

那么a[i][n]就是i走一步匹配到禁忌串的概率。

要把所有步都累加出来,把a[n][n]赋为1就可以了

因为这样下一次计算时b[root][n]=....+b[root][n]*a[n][n]+....

就可以把上次的答案都累加起来了。

自乘x次后,因为贡献永远是1,所以a[root][n]就表示root走x步遇到禁忌串的期望,也就是答案。


最后吐槽一句:卡精度简直丧心病狂...不开long double就不让过...


#include<cstdio>
#include<cstring>
#include<algorithm>
const int maxn=110;
using namespace std;
struct matrix{
	long double a[maxn][maxn];
	void clear(){for (int i=0;i<maxn;i++) for (int j=0;j<maxn;j++) a[i][j]=0.0;}
}ans,f;
int n,K,dsiz,num;char s[maxn];bool bo[maxn];
inline matrix operator*(matrix a,matrix b){
	matrix res;res.clear();
	for (int i=0;i<=n;i++)
		for (int j=0;j<=n;j++)
			for (int k=0;k<=n;k++)
				res.a[i][j]+=a.a[i][k]*b.a[k][j];
	return res;
}
void qpow(){for (;K;K>>=1,f=f*f) if (K&1) ans=ans*f;}

struct AC_DFA{
	int tot,ch[maxn][26],fail[maxn],q[maxn],head,tail;bool end[maxn];
	void insert(){
		int len=strlen(s),p=0;
		for (int i=0;i<len;p=ch[p][s[i]-'a'],i++) if (!ch[p][s[i]-'a']) ch[p][s[i]-'a']=++tot;
		end[p]=1;
	}
	void getfail(){
		head=0,q[tail=1]=0,fail[0]=-1;
		while (head!=tail){
			int x=q[++head];
			for (int i=0;i<dsiz;i++)
				if (ch[x][i]) q[++tail]=ch[x][i],fail[ch[x][i]]=x==0?0:ch[fail[x]][i];
				else ch[x][i]=x==0?0:ch[fail[x]][i];
			end[x]|=end[fail[x]];
		}
	}
	void build(){
		head=0,q[tail=1]=0,bo[0]=1;
		long double tmp=1.0/dsiz;
		while (head!=tail){
			int x=q[++head];
			for (int i=0;i<dsiz;i++){
				if (!bo[ch[x][i]]) bo[ch[x][i]]=1,q[++tail]=ch[x][i];
				if (end[ch[x][i]]) f.a[x][n]+=tmp,f.a[x][0]+=tmp;
				else f.a[x][ch[x][i]]+=tmp;
			}
		}
	}
}T;

int main(){
	scanf("%d%d%d",&num,&K,&dsiz);
	for (int i=1;i<=num;i++) scanf("%s",s),T.insert();
	n=T.tot+1,T.getfail(),T.build();
	for (int i=0;i<=n;i++) ans.a[i][i]=1;
	f.a[n][n]=1;qpow();
	printf("%.7f\n",(double)ans.a[0][n]);
	return 0;
}



你可能感兴趣的:(AC自动机)