P3715 [BJOI2017] 魔法咒语

P3715 [BJOI2017] 魔法咒语

题意

\(n\) 个字符串拼成一个长为 \(L\) 的长串,长串中不能出现另外的 \(m\) 个字符串,求总方案数。

思路

限制条件为忌讳词语不能匹配上拼成的长串。

所以我们把忌讳词语都扔到 AC 自动机上做 dp。

\(S_i\) 表示基本词汇,\(trnas_{u,i}\) 表示 fail 树上节点 \(u\) 拼接上 \(S_i\) 后会转移到的节点。

\(dp_{u,l}\) 为当前在 fail 树上节点 \(u\),拼成串长为 \(l\) 时的方案数,

如果存在一个忌讳词语以节点 \(v\) 结尾,那么 \(v\) 及其子树都不能参与转移,此时我们设其对应的 \(trans_{u,i}=0\)

设 AC 自动机上有 \(c\) 个节点,那么转移很好写:

\[dp_{u,l} = \sum_{v=1}^c \sum_{i=1}^n [trans_{v,i}==u]dp_{v,l-|S_i|} \]

最后答案为 \(ans = \sum_{u=1}^c dp_{u,L}\)

然后我们发现这个玩意的 \(L\) 高达 \(10^{18}\),但当 \(L\) 较大时 \(|S_i|\le2\)。所以我们考虑点分治,对数据点。

Subtask 1

前 60pts 我们直接如上 dp。

Subtask 2

中间 20pts,\(|S_i|=1\)\(l\) 只会对 \(l+1\) 有贡献。

艾佛森括号不再作判断语句,而是放进转移里:

\[dp_{u,l} = \sum_{v=1}^c \sum_{i=1}^n [trans_{v,i}==u]\times dp_{v,l-1} \]

中间的艾佛森括号我们提出来求和:

\[f_{v,u} = \sum_{i=1}^n [trans_{v,i}==u] \]

转移方程变成:

\[dp_{u,l} = \sum_{v=1}^c f_{v,u} \times dp_{v,l-1} \]

转移方程现在已经很像矩阵乘法了,所以我们构造转移矩阵优化 dp。

\[F_l = \begin{bmatrix} dp_{1,l} \\ dp_{2,l} \\ \vdots \\ dp_{c,l} \end{bmatrix} \space T = \begin{bmatrix} f_{1,1} & f_{2,1} & \cdots & f_{c,1} \\ f_{1,2} & f_{2,2} & \cdots & f_{c,2} \\ \vdots & \vdots & \vdots & \vdots \\ f_{1,c} & f_{2,c} & \cdots & f_{c,c} \end{bmatrix} \]

\[F_L = T^{L-1}F_1 \]

Subtask 3

最后 20pts,\(|S_i|\le2\)\(l\) 只会对 \(l+1\)\(l+2\) 有贡献。

把对 \(|S_i|\) 的分类讨论放进转移里:

\[dp_{u,l} = \sum_{v=1}^c \sum_{i=1}^n [|S_i|==1][trans_{v,i}==u]dp_{v,l-1}+[|S_i|==2][trans_{v,i}==u]dp_{v,l-2} \]

分别把两对艾佛森括号提出来:

\[f_{1,v,u} = \sum_{i=1}^n [|S_i|==1][trans_{v,i}==u] \\ f_{2,v,u} = \sum_{i=1}^n [|S_i|==2][trans_{v,i}==u] \]

转移方程变成了:

\[dp_{u,l} = \sum_{u=1}^c f_{1,v,u}\times dp_{v,l-1}+f_{2,v,u}\times dp_{v,l-2} \]

现在在矩阵里记录 \(l-1\)\(l-2\) 两个状态就可以了。

\[F_l = \begin{bmatrix} dp_{1,l-1} \\ dp_{2,l-1} \\ \vdots \\ dp_{c,l-1} \\ \\ dp_{1,l-2} \\ dp_{2,l-2} \\ \vdots \\ dp_{c,l-2} \end{bmatrix} \space T= \begin{bmatrix} f_{1,1,1} & f_{1,2,1} & \cdots & f_{1,c,1} & \space f_{2,1,1} & f_{2,2,1} & \cdots & f_{2,c,1} \\ f_{1,1,2} & f_{1,2,2} & \cdots & f_{1,c,2} & \space f_{2,1,2} & f_{2,2,2} & \cdots & f_{2,c,2} \\ \vdots & \vdots & \vdots & \vdots & \space \vdots & \vdots & \vdots & \vdots \\ f_{1,1,c} & f_{1,2,c} & \cdots & f_{1,c,c} & \space f_{2,1,c} & f_{2,2,c} & \cdots & f_{2,c,c} \\ \\ 1 & 0 & \cdots & 0 & \space 0 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 & \space 0 & 0 & \cdots & 0 \\ \vdots & \vdots & \vdots & \vdots & \space \vdots & \vdots & \vdots & \vdots \\ 0 & 0 & \cdots & 1 & \space 0 & 0 & \cdots & 0 \end{bmatrix} \]

\[F_L = T^{L-1}F_1 \]

代码

const int N = 55,M = 105;
const ll mod = 1e9+7;
char t[M],S[N][M];
int n,m,L,f2 = 1; ll ans;
int rt = 1,cnt = 1;
struct{
	int son[26],fail,trans[N];
	int is_ed; // is_ed 是否有一个忌讳词语以该节点结尾
	vector <int> g; // fail 树边
}tr[M];
inline void insert(){
	int now = rt;
	int len = strlen(t+1);
	for(int i=1;i<=len;++i){
		int c = t[i]-'a';
		if(!tr[now].son[c]) tr[now].son[c] = ++cnt;
		now = tr[now].son[c];
	}
	tr[now].is_ed = 1;
}
inline int get_trans(int now,int id){
	int len = strlen(S[id]+1);
	for(int i=1;i<=len;++i){
		int c = S[id][i]-'a';
		now = tr[now].son[c];
		if(tr[now].is_ed) return 0; // 不能参与转移设成 0
	}
	return now;
}
inline void get_fail(){
	queue <int> q;
	for(int i=0;i<26;++i) tr[0].son[i] = 1;
	q.push(1);
	while(!q.empty()){
		int now = q.front();
		q.pop();
		for(int i=0;i<26;++i){
			if(tr[now].son[i]){
				tr[tr[now].son[i]].fail = tr[tr[now].fail].son[i];
				q.push(tr[now].son[i]);
				// 一个节点不能转移,其子树内节点都不能参与转移
				tr[tr[now].son[i]].is_ed |= tr[tr[tr[now].son[i]].fail].is_ed; 
				tr[tr[tr[now].son[i]].fail].g.push_back(tr[now].son[i]);
			}
			else tr[now].son[i] = tr[tr[now].fail].son[i];
		}
	}
	q.push(1);
	while(!q.empty()){
		int now = q.front();
		q.pop();
		if(tr[now].is_ed) continue;
		for(int i=1;i<=n;++i) tr[now].trans[i] = get_trans(now,i);
		for(int i=tr[now].g.size()-1;i>=0;--i) q.push(tr[now].g[i]);
	}
}

inline void solve1(){ // L<=100
	ll dp[M][M];
	memset(dp,0,sizeof(dp));
	dp[1][0] = 1;
	for(int len=0;len<L;++len){
		for(int now=1;now<=cnt;++now){
			if(!dp[now][len]) continue;
			for(int i=1;i<=n;++i){
				if(tr[now].trans[i] && len+strlen(S[i]+1)<=L){
					(dp[tr[now].trans[i]][len+strlen(S[i]+1)] += dp[now][len])%=mod;
				}
			}
		}
	}
	for(int now=1;now<=cnt;++now) (ans += dp[now][L])%=mod;
}
struct Matrix{
	int row,clm;
	ll val[M<<1][M<<1];
	Matrix(){
		row = clm = 0;
		memset(val,0,sizeof(val));
	}
	Matrix(int f){
		row = clm = 0;
		memset(val,0,sizeof(val));
		if(f) for(int i=(M<<1)-1;i;--i) val[i][i] = 1;
	}
	inline Matrix operator * (const Matrix&G) const{
		Matrix res = Matrix(0);
		res.row = row; res.clm = G.clm;
		for(int k=1;k<=clm;++k){
			for(int i=1;i<=row;++i){
				for(int j=1;j<=G.clm;++j){
					(res.val[i][j] += val[i][k]*G.val[k][j])%=mod;
				}
			}
		}
		return res;
	}
	inline void operator *= (const Matrix&G){
		(*this) = (*this)*G;
	}
	friend inline Matrix quick_pow(Matrix x,int y){
		Matrix res = Matrix(1); 
		res.row = x.row; res.clm = x.clm;
		while(y){
			if(y&1) res *= x;
			x *= x;
			y >>= 1;
		}
		return res;
	}
}T,F;
inline void solve2_1(){ // |S|==1
	F.row = cnt; F.clm = 1;
	for(int i=1;i<=n;++i) F.val[tr[1].trans[i]][1]++;
	T.row = cnt; T.clm = cnt;
	for(int now=1;now<=cnt;++now){
		for(int i=1;i<=n;++i){
			T.val[tr[now].trans[i]][now]++;
		}
	}
	F = quick_pow(T,L-1)*F;
	for(int now=1;now<=cnt;++now) (ans += F.val[now][1])%=mod;
}
inline void solve2_2(){ // |S|<=2
	F.row = cnt*2; F.clm = 1;
	for(int i=1;i<=n;++i) F.val[tr[1].trans[i]][1] += (strlen(S[i]+1)==1);
	F.val[cnt+1][1] = 1; // dp[1][0] = 1
	T.row = cnt*2; T.clm = cnt*2;
	for(int now=1;now<=cnt;++now){
		for(int i=1;i<=n;++i){
			if(strlen(S[i]+1)==1) T.val[tr[now].trans[i]][now]++;
			else if(strlen(S[i]+1)==2) T.val[tr[now].trans[i]][now+cnt]++;
			T.val[now+cnt][now] = 1;
		}
	}
	F = quick_pow(T,L-1)*F;
	for(int now=1;now<=cnt;++now) (ans += F.val[now][1])%=mod;
}
int main(){

	read(n,m,L);
	for(int i=1;i<=n;++i){
		scanf("%s",S[i]+1); 
		if(strlen(S[i]+1)>1) f2 = 0; // Subtask 2 or 3
	}
	for(int i=1;i<=m;++i){
		scanf("%s",t+1);
		insert();
	}
	get_fail();
	L<=100 ? solve1() : f2 ? solve2_1() : solve2_2();	
	printf("%lld",ans);

	return 0;
}
posted @ 2025-06-12 20:31  Tmbcan  阅读(31)  评论(0)    收藏  举报