题解:[BJWC2011] 禁忌

题目传送门

题意分析

\(N,\text{alphabet},T_i\) 分别为 \(n,v,t_i\)

贪心选择

发现 \(s\) 分割成为若干个禁忌串实在太难了,考虑如何高效地对于确定的 \(s\) 求出能分割出的 \(t_i\) 的数量。

观察样例,aabb 的禁忌伤害为 \(1\),划分为 aa bba abb。经过手玩,我们可以发现一种贪心策略是从左往右扫 \(s\),如果匹配到一个禁忌串就立即匹配。

因为禁忌串对于禁忌伤害的贡献都是 \(1\),我们要最大化划分出的禁忌串的数量。而把一个子串留到后面再划分,不能带来更多的禁忌串。

AC 自动机

所以建立 \(t_i\) 的 AC 自动机,从而快速匹配 \(s\) 中的禁忌串数量。

DP

暴力枚举 \(s\) 是无法接受的,考虑 DP 求解。

\(\textit{dp}_{i,j}\) 表示扫到 \(s_i\),处于 AC 自动机节点 \(j\) 时即将到达禁忌串的终止节点,产生禁忌伤害的概率

\(\operatorname{child}_k(j)\) 表示 AC 自动机节点 \(j\) 的代表字符 \(k\) 的子节点,\(0\leq k<v\)。设 \(\textit{id}_j\) 表示 AC 自动机节点 \(j\) 是否为一个禁忌串的终止节点。

  • \(\textit{id}_{\operatorname{child}_k(j)}=1\),则有 \(\textit{dp}_{i,0}\leftarrow\textit{dp}_{i,0}+\dfrac{\textit{dp}_{i-1,j}}v\)。因为 AC 自动机匹配到禁忌串后会回到根节点 \(0\)\(s_i\)\(v\) 种选择。

    此时还要记录期望伤害,\(\textit{ans}\leftarrow\textit{ans}+\dfrac{\textit{dp}_{i-1,j}}v\)

  • 否则有 \(\textit{dp}_{i,\operatorname{child}_k(j)}\leftarrow\textit{dp}_{i,\operatorname{child}_k(j)}+\dfrac{\textit{dp}_{i-1,j}}v\)

\(L\) 为 AC 自动机节点数,得到了 \(\mathcal O(\textit{len}\cdot L)\) 的做法。因为 \(\textit{len}\) 可以达到 \(10^9\),无法接受。

朴素 DP 参考代码
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
#define double long double
constexpr const int N=5,T=15,LEN=1e9,V=26,X=75;
int n,len,v,ans;
string s;
string t[N+1];
struct AC{
	struct node{
		int m[26];
		int fail;
		bool id;
	}t[X+1];
	
	int size;
	void insert(string s,int id){
		int p=0;
		for(char i:s){
			if(!t[p].m[i-'a']){
				t[p].m[i-'a']=++size;
			}
			p=t[p].m[i-'a'];
		}
		t[p].id=true;
	}
	int q[N+1],front,rear;
	void build(){
		for(int i=0;i<V;i++){
			if(t[0].m[i]){
				q[rear++]=t[0].m[i];
			}
		}
		while(front<rear){
	    	int x=q[front++];
	        for(int i=0;i<v;i++){
	        	if(t[x].m[i]){
	        		t[t[x].m[i]].fail=t[t[x].fail].m[i];
	        		t[t[x].m[i]].id|=t[t[t[x].m[i]].fail].id;
	    			q[rear++]=t[x].m[i];
				}else t[x].m[i]=t[t[x].fail].m[i];
			}
		}
	}
	double DP(){
		static double dp[X+1][X+1];
		double ans=0;
		dp[0][0]=1;
		for(int i=1;i<=len;i++){
			for(int j=0;j<=size;j++){
				for(int k=0;k<v;k++){
					if(t[t[j].m[k]].id){
						dp[i][0]+=dp[i-1][j]/v;
						ans+=dp[i-1][j]/v;
					}else{
						dp[i][t[j].m[k]]+=1.0*dp[i-1][j]/v; 
					}
				}
			}
		}
		return ans;
	}
}AC;
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin>>n>>len>>v;
	for(int i=1;i<=n;i++){
		string t;
		cin>>t;
		AC.insert(t,i);
	}
	AC.build();
	cout<<fixed<<setprecision(8)<<AC.DP()<<'\n';
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}

矩阵加速转移

容易发现求解 \(\textit{dp}_{i,j}\) 的时候,与 \(i\) 无关,每次转移都是一样的线性转移。

因此考虑矩阵快速幂加速转移,因为 \(L\leq75\),可以接受。

矩阵乘法加速 DP 其实就是枚举所有 DP 式中的元素,再把 DP 式里的系数填入矩阵,没有就是 \(0\)。不要忘了 \(\textit{ans}\) 对自己的贡献(右下角的 \(1\))。

AC 代码

粘了一个矩阵板子。另外根据实现不同,可能会存在卡精度的问题,或许需要 long double

//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cstdio>
#include<string>
#include<vector>
#include<cmath>
#include<ctime>
#include<deque>
#include<queue>
#include<stack>
#include<list>
using namespace std;
constexpr const int N=5,T=15,LEN=1e9,V=26,X=75;
int n,len,v,ans;
string s;
string t[N+1];
struct Matrix{
	int n,m;
	double a[X+2+1][X+2+1];
	Matrix(int nn=-1,int mm=-1){
		if(mm==-1){
			mm=nn;
		}
		if(nn!=-1){
			n=nn,m=mm;
		}else{
			n=0,m=0;
		}
		for(int i=0;i<X+2+1;i++){
			for(int j=0;j<X+2+1;j++){
				a[i][j]=0;
			}
		}
	}
	Matrix(initializer_list<initializer_list<double>> x){
		n=x.size();m=0;
		for(auto &i:x){
			m=max(m,(int)i.size());
		}
		int pi=0;
		for(auto &i:x){
			int pj=0;
			for(auto &j:i){
				a[pi][pj]=j;
				pj++;
			}
			pi++;
		}
	}
	double* operator [](const int &x){
		return a[x];
	}
	void unit(){
		for(int i=0;i<n;i++){
			for(int j=0;j<m;j++){
				a[i][j]=0;
			}
		}
		for(int i=0;i<n;i++){
			a[i][i]=1;
		}
	} 
	void print(){
		for(int i=0;i<n;i++){
			for(int j=0;j<m;j++){
				cout<<a[i][j]<<' ';
			}
			cout<<endl;
		}
	}
};
Matrix operator *(Matrix A,Matrix B){
	Matrix C(A.n,B.m);
	for(int i=0;i<A.n;i++){
		for(int j=0;j<B.m;j++){
			for(int k=0;k<A.m;k++){
				C[i][j]+=A[i][k]*B[k][j];
			}
		}
	}
	return C;
}
Matrix& operator *=(Matrix &A,Matrix B){
	return A=A*B;
}
Matrix operator +(Matrix A,Matrix B){
	Matrix C(A.n,A.m);
	for(int i=0;i<A.n;i++){
		for(int j=0;j<A.m;j++){
			C[i][j]=A[i][j]+B[i][j];
		} 
	} 
	return C;
}
Matrix& operator +=(Matrix &A,Matrix B){
	for(int i=0;i<A.n;i++){
		for(int j=0;j<A.m;j++){
			A[i][j]+=B[i][j];
		}
	}
	return A;
}
Matrix qpow(Matrix base,int n){
	Matrix ans(base.n);
	ans.unit();
	while(n){
		if(n&1){
			ans=base*ans;
		}
		base=base*base;
		n>>=1;
	}
	return ans;
}
struct AC{
	struct node{
		int m[26];
		int fail;
		bool id;
	}t[X+1];
	
	int size;
	void insert(string s,int id){
		int p=0;
		for(char i:s){
			if(!t[p].m[i-'a']){
				t[p].m[i-'a']=++size;
			}
			p=t[p].m[i-'a'];
		}
		t[p].id=true;
	}
	int q[X+1],front,rear;
	void build(){
		for(int i=0;i<v;i++){
			if(t[0].m[i]){
				q[rear++]=t[0].m[i];
			}
		}
		while(front<rear){
	    	int x=q[front++];
	        for(int i=0;i<v;i++){
	        	if(t[x].m[i]){
	        		t[t[x].m[i]].fail=t[t[x].fail].m[i];
	        		t[t[x].m[i]].id|=t[t[t[x].m[i]].fail].id;
	    			q[rear++]=t[x].m[i];
				}else t[x].m[i]=t[t[x].fail].m[i];
			}
		}
	}
	double DP(){
		Matrix dp(size+2,1),f(size+2,size+2);
		dp[0][0]=1;
		for(int j=0;j<=size;j++){
			for(int k=0;k<v;k++){
				if(t[t[j].m[k]].id){
//					dp[i][0]+=dp[i-1][j]/v;
					f[0][j]+=1.0/v;
//					ans+=dp[i-1][j]/v;
					f[size+1][j]+=1.0/v;
				}else{
//					dp[i][t[j].m[k]]+=1.0*dp[i-1][j]/v; 
					f[t[j].m[k]][j]+=1.0/v;
				}
			}
		}
		f[size+1][size+1]=1;
		dp=qpow(f,len)*dp;
		return dp[size+1][0];
	}
}AC;
int main(){
	/*freopen("test.in","r",stdin);
	freopen("test.out","w",stdout);*/
	
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	
	int n;
	cin>>n>>len>>v;
	for(int i=1;i<=n;i++){
		string t;
		cin>>t;
		AC.insert(t,i);
	}
	AC.build();
	cout<<fixed<<setprecision(8)<<AC.DP()<<'\n';
	
	cout.flush();
	
	/*fclose(stdin);
	fclose(stdout);*/
	return 0;
}
posted @ 2026-06-28 14:25  TH911  阅读(2)  评论(0)    收藏  举报