题意:给几个模式串,找26^m次方个串中出现这些模式串的个数。(即方案数。
题解:逆向思维,算是一个简单的容斥原理,找到所有不含有模式串的个数,剪掉。方法就是f[i][j]来统计在第i位(i<=m)时,匹配到第j个结点的方案数。最后求和,做差就好了。
起初总想不明白如何转移的,最后想想它是把26个字母都遍历一遍进行转移,就差不多。
转移方程:f[i+1][v] = (f[i+1][v] + f[i][j])%mod。v见下面的代码。转移的话,就是之前的方案数,加上这一次的方案数,同一个i的循环中有时会很多跳回之前的结点,从而递增。例如i == 3,由于失配指针,出现了f[4][0]了多次(例子与题目无关)从而递加。
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
const int maxn = 10010,alpha = 26,maxm = 110,mod = 10007;
int f[maxn],cnt[maxm][maxn],pc,ch[maxn][alpha];
char ptr[maxm];
bool flag[maxn];
void ins(char *s)
{
int cur = 0,n = strlen(s);
for (int i=0;i<n;i++)
{
int c = s[i] - 'A';
if (!ch[cur][c]) ch[cur][c] = ++pc;
cur = ch[cur][c];
}
flag[cur] = true;
}
void getfail()
{
queue<int> q;
for (int c=0;c<alpha;c++)
if (ch[0][c]) q.push(ch[0][c]);
while (!q.empty())
{
int r = q.front();q.pop();
for (int c=0;c<26;c++)
{
int u = ch[r][c];
if(!u) continue;
q.push(u);
int j = f[r];
while (!ch[j][c] && j) j = f[j];
j = ch[j][c];
f[u] = j;
if (flag[f[u]]) flag[u] = true;
//这一个很关键,他转移过去的模式串出现了在文本中,所以他也就不能被dp转移
}
}
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);
while(n--)
{
scanf(" %s",ptr);
//scanf中的空格据大佬说可以避免不同系统的不同结束情况引起的错误,如\n,\r\n。会被空格吸收
ins(ptr);
}
getfail();
cnt[0][0] = 1;
for (int i=0;i<m;i++)
{
for (int j=0;j<=pc;j++)
{
if (!cnt[i][j] && flag[j]) continue;
//cnt为0即前面有模式串出现,flag为true即当前j位出现模式串,所以跳过
for (int c=0;c<alpha;c++)
{
int tmp = j;
while(tmp && !ch[tmp][c]) tmp = f[tmp];
//这个是说没有子结点就跳失配指针,没有找到就是回归root结点
int v = ch[tmp][c];
if (!flag[v]) cnt[i+1][v]=(cnt[i+1][v]+cnt[i][j])%mod;
}
}
}
int ans = 0,sum = 1;
for(int i=0;i<m;++i)
sum = sum * 26 %mod;
for(int i=0;i<=pc;++i){
if(!flag[i]) ans = (ans + cnt[m][i]) % mod;
}
printf("%d\n",(sum - ans + mod)%mod);
return 0;
}
做题时借鉴了此文:https://blog.csdn.net/zhhx2001/article/details/52137250