Luogu P2536 [AHOI2005]病毒检测
题意
给一个有通配符的模式串和 \(n\) 个文本串,其中 ?
可以匹配任意字符,*
可以匹配 \(0\) 或任意多个字符,求 \(n\) 个文本串中无法与模式串匹配的数量。
\(\texttt{Data Range:}1\leq n\leq 500\)
题解
非确定性有限状态决策自动机。
首先考虑对模式串建一个自动机。对于每一位我们可以抽象成一个节点,整个字符串可以抽象成一个转移图。在匹配的过程中,如果能够走到这个节点上来就说明能够与这个位置的前缀相匹配。这里需要讨论一下:
-
如果模式串的某一位为字母,那么将这一位的状态向下一位的状态连这个字母的边,也就是说只有这个字母能从这一位转移到下一位。
-
如果模式串的某一位为
?
,那么任何字母进来都能匹配上,也就是说将这一位的状态向下一位的状态连所有字母的边。 -
如果模式串的某一位为
*
,这里是个小 trick,也就是说可以匹配任意长度的串,那就连个自环就可以了。
然后,对于匹配的话,从初始状态开始走,如果能够走到结束状态那么则匹配成功。
代码非常好写,跑得也很快。
代码
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=1e3+51;
struct Node{
ll isEnd;
vector<ll>nxt[4];
};
Node x[MAXN];
vector<ll>s,nxt;
ll n,len,tot,c,flg,res;
ll mp[128],vis[MAXN];
char ch[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline void match()
{
s.clear(),s.push_back(1);
for(register int i=1;i<=len;i++)
{
nxt.clear(),flg=0;
for(register int j=1;j<=tot;j++)
{
vis[j]=0;
}
for(register int j=0;j<s.size();j++)
{
for(register int k=0;k<x[s[j]].nxt[mp[ch[i]]].size();k++)
{
if((c=x[s[j]].nxt[mp[ch[i]]][k])&&!vis[c])
{
vis[c]=1,nxt.push_back(c),flg|=x[c].isEnd;
}
}
}
s=nxt;
}
res+=1-flg;
}
int main()
{
scanf("%s",ch+1),len=strlen(ch+1),tot=1,memset(mp,-1,sizeof(mp));
mp['A']=0,mp['G']=1,mp['C']=2,mp['T']=3;
for(register int i=1;i<=len;i++)
{
if(mp[ch[i]]!=-1)
{
x[tot].nxt[mp[ch[i]]].push_back(tot+1),tot++;
continue;
}
if(ch[i]=='?')
{
x[tot].nxt[0].push_back(tot+1),x[tot].nxt[1].push_back(tot+1);
x[tot].nxt[2].push_back(tot+1),x[tot].nxt[3].push_back(tot+1);
tot++;
continue;
}
if(ch[i]=='*')
{
x[tot].nxt[0].push_back(tot),x[tot].nxt[1].push_back(tot);
x[tot].nxt[2].push_back(tot),x[tot].nxt[3].push_back(tot);
continue;
}
}
x[tot].isEnd=1,n=read();
for(register int i=0;i<n;i++)
{
scanf("%s",ch+1),len=strlen(ch+1),match();
}
printf("%d\n",res);
}