P4569 [BJWC2011]禁忌

题目传送门

题意简述:给出大小为 \(n\) 的字典 \(s\)。设函数 \(g(t)\) 表示 \(t\) 最多能被分割成的单词个数。等概率随机生成长度为 \(len\) 的字符串 \(T\),求 \(E(g(t))\)。

hot tea. 比较像 P3193 [HNOI2008]GT考试


首先对 \(s_i\) 建出 ACAM,然后在上面 DP。设 \(f_{i,j}\) 表示关于所有 \(T\)(\(|T|=i\) 且 \(T\) 在 ACAM 上能跑到状态 \(j\))的一个东西。那么究竟是表示什么呢?

记 \(P=\dfrac{f_{i,j}}{a}\);\(p\) 为 \(j\) 的所有子节点,即 \(p\in son_j\)。

错误思路:如果 \(f_{i,j}\) 单纯表示字符串 \(T\) 的 “概率”(即 \(\dfrac{num(T)}{a^i}\)),那么转移与统计答案就是:如果 \(p\) 是终止节点,将 \(ans\) 加上 \(P\);同时将所有 \(f_{i+1,p}\) 加上 \(P\)。不错,如果 \(g(T)\) 表示的是所有单词 \(s_i\) 在 \(T\) 中出现次数之和,那么这样是没错的,可惜不是,因为会有重复计算,即若字典 \(s=\{\texttt{ab,bc}\}\),那么 \(T=\{\texttt{abc}\}\) 会算入两次贡献(样例中也有提到这种情况)。

正确思路:注意到这个 "最多" 有点麻烦,不过还是有处理的办法:如果 \(p\) 是一个终止节点,那么在下传概率的时候,将 \(f_{i+1,0}\)(而不是 \(f_{i+1,p}\))加上 \(P\)。如果成功匹配一个单词,就必须从头开始匹配。

注意到 \(\sum |s_i|\) 很小,只有 \(75\)。这也意味着 \(j\) 的范围只有 \(75\)。因此,用矩阵快速幂加速 DP 即可,别忘了在矩阵中留个位置记录 \(ans\)。

总时间复杂度 \(\mathcal{O}((\sum T)^3\log len)\)。

/*
	Powered by C++11.
	Author : Alex_Wei.
*/

#include <bits/stdc++.h>
using namespace std;

//#pragma GCC optimize(3)
//#define int long long

using uint = unsigned int;
using ll = long long;
using ull = unsigned long long;
using db = double;
using ld = long double;
using pii = pair <int,int>;
using pll = pair <ll,ll>;
using pdd = pair <double,double>;
using vint = vector <int>;
using vpii = vector <pii>;

#define fi first
#define se second
#define pb emplace_back
#define mpi make_pair
#define all(x) x.begin(),x.end()
#define sor(x) sort(all(x))
#define rev(x) reverse(all(x))
#define mem(x,v) memset(x,v,sizeof(x))
#define mcpy(x,y) memcpy(x,y,sizeof(y))

const int N=77;
const int S=26;

int sz,al,son[N][S],fa[N],ed[N];
void ins(string s){
	int p=0;
	for(char it:s){
		if(!son[p][it-'a'])son[p][it-'a']=++sz;
		p=son[p][it-'a'];
	} ed[p]=1;
}
void build(){
	queue <int> q;
	for(int i=0;i<al;i++)if(son[0][i])q.push(son[0][i]);
	while(!q.empty()){
		int t=q.front(); q.pop();
		for(int i=0;i<al;i++)
			if(son[t][i])q.push(son[t][i]),fa[son[t][i]]=son[fa[t]][i];
			else son[t][i]=son[fa[t]][i];
		ed[t]|=ed[fa[t]];
	}
}
struct mat{
	ld a[N][N];
	friend mat operator * (mat x,mat y){
		mat z; mem(z.a,0);
		for(int i=0;i<=sz;i++)
			for(int j=0;j<=sz;j++)
				for(int k=0;k<=sz;k++)
					z.a[i][j]+=x.a[i][k]*y.a[k][j];
		return z;
	}
}base,ans;

int n,len;
int main(){
	cin>>n>>len>>al;
	for(int i=0;i<n;i++){
		string s;
		cin>>s,ins(s);
	} build();
	for(int i=0;i<=sz;i++)
		for(int j=0;j<al;j++){
			int p=son[i][j];
			if(ed[p])base.a[i][0]+=1.0/al,base.a[i][sz+1]+=1.0/al;
			else base.a[i][p]+=1.0/al;
		}
	sz++,ans.a[0][0]=base.a[sz][sz]=1;
	while(len){
		if(len&1)ans=ans*base;
		base=base*base,len>>=1;
	} printf("%.10Lf\n",ans.a[0][sz]);
	return 0;
}
上一篇:Secrets of the JavaScript Ninja


下一篇:【YBTOJ】【Luogu P4570】[BJWC2011]元素