洛谷P17513题解(AC自动机+矩阵加速)

洛谷P17513题解(AC自动机+矩阵加速)

题目链接

题意

填充一串长度为L的字符串,同时有两个约束

  • A:字符串的子串不能出现某些禁止串

  • B:同时有些位置一定要填特定的字符串

A类约束的数目为\(N,N<=50\),禁止串的总长度\(s_i\)满足\(\sum S_i<=100\)

B类约束的数目为\(K,K<=500\),

\(L<=10^{18}\)

字符集为小写字母表

思路

不能出现某些特定的子串,这提醒我们如果要判断某个位置\(i\)填什么字符的话,一定要考虑前面填了什么字符,不能在填了当前这个字符后出现禁止串,这提醒我们用AC自动机判断当前填了这个字符的状态是否合法,具体过程如下:

用所有禁止串构建AC自动机,并且标记哪些节点路径上代表的字符串后缀是禁止串,这样的节点是不合法的。

维护一个长度为 AC自动机(的trie)的节点总数 的dp数组,dp[i]表示选了某个字符之后到达AC自动机的\(i\)节点时的方案数。如果\(i\)节点被标记了就把转移到i节点的方案放弃,否则更新到下一轮dp的方案,这个位置考虑完后交换当前dp和下一轮dp的答案。如果当前位置必须填限定字符,那就只考虑前一位的方案数到当前这个限定字符的方案。

但是L的长度非常大,如果我们直接遍历1e18无疑TLE,但是B很小表示限定的位置是稀疏的,如果我们能够快速压缩限定位置之间的打断“空白”,就能很快的加速这个转移过程。

考虑用矩阵加速。

矩阵的第i行第j列表示从AC自动机的i状态转移到AC自动机的j状态可以走的字母选择种数。每次碰到一段没有限制的“空白”时,就可以用dp[]*mat去前往下一个dp,利用交换律转换成矩阵与矩阵之间的快速幂即可在log的时间内算完一段长度

然而这样的话复杂度是\(O(K M^3log L)\),因为每一段“空白”(设长度为g)我们都采取了\(M^3log\ g\)的快速幂乘法。这样复杂度爆炸,会TLE

优化

之前复杂度爆炸的原因在于我们在遇到一段“空白”时,都选择用矩阵快速幂计算\(M^g\),快速幂的复杂度是\(O(M^3log \ g)\)的。如果我们每次可以直接得到这个\(M^g\)的话,那么就直接用\(dp*M^g\),在\(O(M*M)\)的复杂度下即可算完每一段空白。

考虑预处理矩阵的二进制次方快速幂\(M^{(2^{k})}\),\((1<<k_{max})>=1e18\),那么我们每次乘以一个\(M^k\),\(g \& (1<<k)=1\),做log次乘法就可以算出\(dp*M^g\),每次向量乘以矩阵的乘法复杂度是\(O(M^2log\ g)\),填上前面的段数和预处理矩阵乘法,综合的复杂度就是\(O(M^3log \ L+KM^2log \ L)\)。这个复杂度是可以接受的。

代码

#include<bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define dbg(x) cerr<<#x<<':'<<x<<'\n'
#else
#define dbg(x) ((void)0)
#endif
#define all(x) (x).begin(),(x).end()

#define int long long
#define vi vector<int> 
#define endl '\n'
const int MOD=998244353;
int utot;
int trie[105][26];
int fail[105];
int mat[61][105][105];
int alert[105];
void ins(string s){
	int u=0;
	int ch,len=s.size();
	for(int i=0;i<len;i++){
		ch=s[i]-'a';
		if(!trie[u][ch]) trie[u][ch]=++utot;
		u=trie[u][ch];
	}
	alert[u]=1;
}

void buildfail(){
	queue<int>q;
	for(int i=0;i<26;i++){
		if(trie[0][i]) q.push(trie[0][i]);
	}
	while(!q.empty()){
		int u=q.front();q.pop();
		alert[u]|=alert[fail[u]];
		for(int i=0;i<26;i++){
			int& v=trie[u][i];
			if(v){
				fail[v]=trie[fail[u]][i];
				q.push(v);
			}else{
				v=trie[fail[u]][i];
			}
		}
	}
}

void initmat(){
	for(int i=0;i<=utot;i++){
		for(int j=0;j<26;j++){
			if(!alert[trie[i][j]]) mat[0][i][trie[i][j]]++;
		}
	}
	for(int g=1;g<=60;g++)
	for(int i=0;i<=utot;i++)
	for(int j=0;j<=utot;j++)
		for(int k=0;k<=utot;k++){
			mat[g][i][j]=(mat[g][i][j]+mat[g-1][i][k]*mat[g-1][k][j]%MOD)%MOD;
		}
}

void solve(){
	int l,k,n;
	cin>>l>>k>>n;
	map<int,int>mp;
	int pos;char ch;
	for(int i=0;i<k;i++){
		cin>>pos>>ch;
		mp[pos]=ch-'a';
	}		
	string s;
	for(int i=0;i<n;i++){
		cin>>s;
		ins(s);
	}
	buildfail();
	initmat();
	vi dp(utot+1);
	dp[0]=1;
	int lst=0;
	for(auto [pos,ch]:mp){
		int gap=pos-lst-1;
		for(int b=0;gap>0;b++,gap>>=1){
			if(!(gap&1)) continue;
			vi nxt(utot+1);
			for(int i=0;i<=utot;i++){
				for(int j=0;j<=utot;j++){
					nxt[i]=(nxt[i]+dp[j]*mat[b][j][i]%MOD)%MOD;
				}
			}
			swap(dp,nxt);
		}
		vi nxt(utot+1);
		for(int i=0;i<=utot;i++){
			if(!alert[trie[i][ch]]) nxt[trie[i][ch]]=(nxt[trie[i][ch]]+dp[i])%MOD;
		}
		swap(dp,nxt);
		lst=pos;
	}
	int gap=l-lst;
	for(int b=0;gap>0;b++,gap>>=1){
		if(!(gap&1)) continue;
		vi nxt(utot+1);
		for(int i=0;i<=utot;i++){
			for(int j=0;j<=utot;j++){
				nxt[i]=(nxt[i]+dp[j]*mat[b][j][i]%MOD)%MOD;
			}
		}
		swap(dp,nxt);
	}	
	int ans=0;
	for(int i=0;i<=utot;i++) ans=(ans+dp[i])%MOD;
	cout<<ans<<endl;
}

signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);
	int T=1;
	// cin>>T;
	while(T--)
		solve();
	return 0;
}
posted @ 2026-10-01 19:28  江蝶  阅读(2)  评论(0)    收藏  举报