洛谷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;
}

浙公网安备 33010602011771号