第一次做ac自动机+dp的题。因为前日做过一道字符串dp题,这题做起来相对没那么困难一些。觉得一时间这题无法下手可以先试试这场div3的F题:https://blog.csdn.net/weixin_43262291/article/details/98390702
题意:给你n个模式串,现在构造出m长度仅有26个字母的文本串,使其中至少包含1个模式串,问有多少个。
思路:很容易想到dp,dpij,i表示构造到第几位,j来枚举ac自动机的状态。在每个状态枚举26个字母转移到新状态。不过在尝试直接构造的情况下会发现,当构造出来一个包含模式串的状态时,转移到下一个状态,并不知道在个状态哪一部分是合法来的哪一部分是不合法的。所以可以利用补集,把到达合法状态的筛掉,那么达到最终状态的dpmj累加起来取26^m的补集则是最终答案。
细节部分要注意:
- 当前面某一个状态的后缀或者说失配对应位置如果包含模式串,那么剩下的也都得包含,所以预处筛掉,这里我在build函数里加了个if(cnt[fail[u]])cnt[u] = 1;如此一层一层往上会层层标记恰好达到效果。
- 每次筛掉的是下一个状态是不是,也可算当前状态是不是。
代码:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define forn(i,n) for(int i=0;i<n;i++)
#define for1(i,n) for(int i=1;i<=n;i++)
#define IO ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int maxn = 6e3+5;
const int mod = 1e4+7;
int dp[105][maxn],fail[maxn],trie[maxn][26],id,cnt[maxn];
class Aho{
public:
void insert(string s){
int len = s.size(),u = 0;
forn(i,len){
int x = s[i]-'A';
if(!trie[u][x]) trie[u][x] = ++id;
u = trie[u][x];
}
cnt[u]++;
}
queue<int>q;
void build(){
forn(i,26) if(trie[0][i]) q.push(trie[0][i]);
while(!q.empty()){
int u = q.front();q.pop();
if(cnt[fail[u]]) cnt[u] = 1;
forn(i,26){
if(trie[u][i]) fail[trie[u][i]] = trie[fail[u]][i],q.push(trie[u][i]);
else trie[u][i] = trie[fail[u]][i];
}
}
}
}aho;
ll powmod(ll a,ll b){
ll res = 1;
while(b){
if(b&1) res = res*a%mod;
a = a*a%mod;
b>>=1;
}
return res;
}
int main(){
IO;
int n,m;cin>>n>>m;
forn(i,n){
string s;cin>>s;
aho.insert(s);
}
aho.build();
dp[0][0] = 1;
forn(i,m){
forn(j,id)if(!cnt[j]){
forn(k,26){
(dp[i+1][trie[j][k]]+=dp[i][j])%=mod;
}
}
}
ll ans = powmod(26,m);
forn(i,id+1) if(!cnt[i]) (ans=ans+mod-dp[m][i])%=mod;
cout << ans <<'
';
return 0;
}