作为一道蹂躏了我一天的题…我也没啥好说的了QAQ…
思路概述
我们知道,AC自动机是一种多模字符串匹配算法。构造 Trie 树 后,在模式串末尾一位的结点作上标记。平常的 AC自动机 是尽量能多接触到这些标记,使总值最大。本题倒是有点奇葩,要构造一个可行的无限长文本串,使没有任何子串为给出模式串中的一个。也就是说,我们需要让平常 AC自动机 的查询操作,尽量避免标记,能用失配指针跳转就跳转。
因为要有无限长的可行串,根据 AC自动机 的原理,我们可以将结点连接到儿子的边当作一条单向边,同时失配指针也当作一条单向边,如果存在一个环,且环上没有任何危险标记(即病毒代码段末尾一位的结点专门作的编号),此时 AC自动机 能一直在环上匹配,并且永远也不会得到为模式串的一个子串,就像程序中的死循环一样。这个找环我们可以通过 dfs 来实现。
注意事项
1 . 我们需要建立两个布尔数组,其中一个布尔数组记录每个节点在当前 dfs 走的路径上有没有被选中,另一个布尔数组记录每个节点历史上有没有被访问过。如果当前路径形成回路,就找到环了,应该还是比较好实现的。
2 . 避免危险标记,也就是说如果下一个结点拥有危险标记,就不走那个结点。
3 . 在构造失配指针时,一个很明显的优化是:如果一个结点拥有了失配指针,它指向的结点如果有危险标记,自己必然也危险,因为它到根结点形成的串是自己到根节点的后缀。
4.为什么要两个bool标记?因为如图1,如果不能保证当前dfs时在sta中的节点是链式结构的话,就会可能出现误判为环的可能。
5.为什么要在造指针的时候做优化,除了时间上的考虑似乎还有别的因素…如图吧。否则的话,如果只访问子节点,那永远成不了环。访问fail节点的话,又可能自环。
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<algorithm>
#include<cstring>
#include<string>
using namespace std;
struct node{
node*next1[2];
node*fail;
bool tag,vis,insta;//tag标记是否为单词结尾,vis表示此节点是否被访问过,insta表示此节点是否正在被访问的链上
node(){
memset(next1,0,sizeof(next1));fail=NULL;tag=vis=insta=false;
}
}*root=new node();
int cnt=0;
void insert(string str)
{
node*p=root;
for(int i=0;i<str.size();i++){
int x=str[i]-'0';
if(!p->next1[x]){
node*t=new node();p->next1[x]=t;
}
p=p->next1[x];
}
p->tag=true;
}
void build_fail_pointer(void)
{
node*p,*temp;
queue<node*>que;que.push(root);
while(!que.empty()){
temp=que.front();que.pop();
for(int i=0;i<2;i++){
if(temp->next1[i]){
if(temp==root){
temp->next1[i]->fail=root;
}
else{
p=temp->fail;
while(p){
if(p->next1[i]){
temp->next1[i]->fail=p->next1[i];
if(p->next1[i]->tag)temp->next1[i]->tag=true;//如果指向的失配节点是结尾,考虑到失配节点到root的过程中
//是一个单词,那么拥有相同后缀的原节点自然也是结尾.优化要放在这里否则可能出现遗漏
break;
}
p=p->fail;
}
if(!p)temp->next1[i]->fail=root;
}
que.push(temp->next1[i]);
}
else temp->next1[i]=temp->fail->next1[i];//优化,如果没有下一个节点,那么该节点就指向失配指针的对应节点
//不过这个优化放在有ac_automation函数的题目中似乎会re,具体原因暂时不明
}
}
}
void dfs(node*p)
{
p->vis=p->insta=true;
// if(p->fail&&p->fail->tag){
// p->insta=false;return;
// }
if(p->next1[0]){
if(p->next1[0]->insta){//这就解决了所有在sta中的节点都被访问过而不能再访问的问题,确保不会重复访问而且可以正确判断成环
cout<<"TAK"<<endl;exit(0);
}
if(!p->next1[0]->tag&&!p->next1[0]->vis)
dfs(p->next1[0]);
}
if(p->next1[1]){
if(p->next1[1]->insta){
cout<<"TAK"<<endl;exit(0);
}
if(!p->next1[1]->tag&&!p->next1[1]->vis)
dfs(p->next1[1]);
}
p->insta=false;
}
int main()
{
int n,i,j;string str;
cin>>n;
for(i=1;i<=n;i++){
cin>>str;insert(str);
}
build_fail_pointer();
dfs(root);
cout<<"NIE"<<endl;
return 0;
}