bzoj2553 禁忌 AC自动机&矩阵乘法

    题目叙述比较烦。。一句话,在字母只有前alphabet时,给定N个串,求长度为len的串包含这些N个串的个数最大值的期望值。

    观察数据发现串的总长度<=75,所以想到AC自动机;而len大小为10^9级别,就知道肯定是在AC自动机上的矩阵乘法,这样大致的框架就好了。

    如果令a[k][x][y]表示经过k步从状态(AC自动机的节点)x-状态y的期望值。显然转移的过程是一样的,即a[k-1]*c=a[k],那么在AC自动机上,如果有x-y的边,则c[x][y]+=1/alphabet。

    接下来是统计答案。如果已知一个串,则伤害显然是贪心地不断取得到的,即在AC自动机上得到一个目标节点x(即=给定的某一个串),就需要回到原点并更新答案。不妨新增一个点T,那么c[x][0]+=1/alphabet,c[x][T]+=1/alphabet。可以看到,最后需要求的就是经过m部从0-T的期望值。即a[m][0][T]。

    注意为了避免卡精度要开long double。

AC代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#define ld long double
#define N 105
using namespace std;

int n,m,p,now,tot,ch[N][26],h[N],fail[N]; bool flag[N]; char s[N]; struct mat{ ld a[N][N]; };
mat times(mat x,mat y){
	mat z; int i,j,k;
	for (i=0; i<=tot; i++) for (j=0; j<=tot; j++){
		z.a[i][j]=0; for (k=0; k<=tot; k++) z.a[i][j]+=x.a[i][k]*y.a[k][j];
	}
	return z;
}
mat ksm(mat x,int k){
	mat ans=x; for (k--; k; x=times(x,x),k>>=1) if (k&1) ans=times(ans,x); return ans;
}
int main(){
	scanf("%d%d%d",&n,&m,&p); int i,j; tot=0;
	for (i=1; i<=n; i++){
		now=0; scanf("%s",s);
		for (j=0; s[j]; j++){
			int x=s[j]-'a'; if (!ch[now][x]) ch[now][x]=++tot; now=ch[now][x];
		}
		flag[now]=1;
	}
	int head=0,tail=0; for (i=0; i<p; i++) if (ch[0][i]) h[++tail]=ch[0][i];
	while (head<tail){
		int x=h[++head],y;
		for (i=0; i<p; i++) if (y=ch[x][i]){
			h[++tail]=y; fail[y]=ch[fail[x]][i];
			flag[y]|=flag[fail[y]];
		} else ch[x][i]=ch[fail[x]][i];
	}
	tot++; ld val=1.0/p; mat t; memset(t.a,0,sizeof(t.a));
	for (i=0; i<tot; i++) for (j=0; j<p; j++)
		if (flag[ch[i][j]]){ t.a[i][0]+=val; t.a[i][tot]+=val; }else t.a[i][ch[i][j]]+=val;
	t.a[tot][tot]=1; printf("%.10lf\n",(double)ksm(t,m).a[0][tot]);
	return 0;
}

by lych

2016.2.6



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值