zoukankan      html  css  js  c++  java
  • 「字符串算法」第5章 AC自动机课堂过关

    「字符串算法」第5章 AC自动机课堂过关

    洛谷模板题

    P3808 【模板】AC自动机(简单版)

    题目

    题目描述

    给定 (n) 个模式串 (s_i) 和一个文本串 (t),求有多少个不同的模式串在文本串里出现过。
    两个模式串不同当且仅当他们编号不同。

    输入格式

    第一行是一个整数,表示模式串的个数 (n)
    (2) 到第 ((n + 1)) 行,每行一个字符串,第 ((i + 1)) 行的字符串表示编号为 (i) 的模式串 (s_i)
    最后一行是一个字符串,表示文本串 (t)

    输出格式

    输出一行一个整数表示答案。

    输入输出样例

    输入 #1

    3
    a
    aa
    aa
    aaa
    

    输出 #1

    3
    

    输入 #2

    4
    a
    ab
    ac
    abc
    abcd
    

    输出 #2

    3
    

    输入 #3

    2
    a
    aa
    aa
    

    输出 #3

    2

    说明/提示

    • 样例 1 解释

      (s_2)(s_3) 编号(下标)不同,因此各自对答案产生了一次贡献。

      样例 2 解释

      (s_1)(s_2)(s_4) 都在串 abcd 里出现过。

      数据规模与约定

      • 对于 (50\%) 的数据,保证 (n = 1)
      • 对于 (100\%) 的数据,保证 (1 leq n leq 10^6)(1 leq |t| leq 10^6)(1 leq sumlimits_{i = 1}^n |s_i| leq 10^6)

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 1500010
    using namespace std;
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int end[N];
    	int fail[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(fail , 0 , sizeof(fail));
    		memset(end , 0 , sizeof(end));
    		cnt = 1;
    	}
    	int insert(char *s) {
    		
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int key = s[i] - 'a';
    			if(trie[p][key] == 0)
    				trie[p][key] = ++cnt;
    			p = trie[p][key];
    		}
    		end[p]++;
    		return p;
    	}
    	void kmp() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = 1;
    		q.push(root);
    		fail[root] = 0;
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)
    					trie[p][i] =  trie[fail[p]][i];
    				else {
    					q.push(trie[p][i]);
    					fail[trie[p][i]] = trie[fail[p]][i];
    				}
    			}
    		}
    	}
    	int query(char *s) {
    		int len = strlen(s);
    		int ans = 0;
    		int p = root;
    		for(int i = 0 ; i < len ; i++) {
    			int key = s[i] - 'a';
    			int k = trie[p][key];
    			while(k > 1 && ~end[k]) {//注意这里的优化,否则被"aaa..."的数据卡掉
    				ans += end[k];
    				end[k] = -1;
    				k = fail[k];
    			}
    			p = trie[p][key];
    		} 
    		return ans;
    	}
    } AC;
    char s[1000010];
    int n;
    int main() {
    	int T = 1;
    	while(T--) {
    		AC.clear();
    		scanf("%d" , &n);
    		for(int i = 1 ; i <= n ; i++)
    			scanf("%s" , s) , AC.insert(s);
    		AC.kmp(); 
    		scanf("%s" , s);
    		printf("%d
    " , AC.query(s));
    	}
    	return 0;
    }
    

    P5357 【模板】AC自动机(二次加强版)

    题目

    题目描述

    给你一个文本串 (S)(n) 个模式串 (T_{1..n}),请你分别求出每个模式串 (T_i)(S) 中出现的次数。

    输入格式

    第一行包含一个正整数 (n) 表示模式串的个数。

    接下来 (n) 行,第 (i)行包含一个由小写英文字母构成的字符串 (T_i)

    最后一行包含一个由小写英文字母构成的字符串 (S)

    数据不保证任意两个模式串不相同

    输出格式

    输出包含 (n) 行,其中第 (i) 行包含一个非负整数表示 (T_i)(S) 中出现的次数。

    输入输出样例

    输入 #1

    5
    a
    bb
    aa
    abaa
    abaaa
    abaaabaa
    

    输出 #1

    6
    0
    3
    2
    1
    

    说明/提示

    (1le nle 2 imes10^5)(T_{1..n}) 的长度总和不超过 (2 imes 10^5)(S) 的长度不超过 (2 imes10^6)

    思路

    为什么把这道题放到前面呢?简单啊

    想想可以知道上面那题的优化在这里不管用.

    再想一下,搜索文章时,每遍历一个字符就要向上跳fail指针,同一个结点,更新的其他点都是固定的,那么我们是否可以考虑像线段树一样用懒标记维护呢?答案显然是肯定的(不然我就不会说),最后把所有懒标记"上传"(毕竟fail指向的时深度更小的结点嘛),这题就解决了.
    又考虑到fail指向深度更小的结点,所以"上传"的时候要按照深度从大到小的顺序.

    这题的这个思想非常重要,下面很多题都是用类似懒标记的方法完成的!

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 200010
    using namespace std;
    bool vis[N];
    int q[N * 27] , h , t;//开27倍
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int end[N];
    	int fail[N];
    	int tag[N];//其实tag和dat可以合二为一,但是为了好看就分开了
    	int dat[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(end , 0 , sizeof(end));
    		memset(fail , 0 , sizeof(fail));
    		memset(tag , 0 , sizeof(tag));
    		cnt = root;
    	}
    	int insert(char *s) {
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'a';
    			if(trie[p][c] == 0)
    				trie[p][c] = ++cnt;
    			p = trie[p][c];
    		}
    		++end[p];
    		return p;
    	}
    	void build() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)	trie[p][i] = trie[fail[p]][i];
    				else {
    					fail[trie[p][i]] = trie[fail[p]][i];
    					q.push(trie[p][i]);
    				}
    			}
    		}
    	}
    	void article(char *s) {
    		int len = strlen(s);
    		int p = root;
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'a';
    			p = trie[p][c];
    			++tag[p];
    			++dat[p];
    		}
    	}
    	void spread() {
    		q[t++] = root;
    		while(h < t) {
    			vis[q[h]] = true;
    			int p = q[h];
    			for(int i = 0 ; i < 26 ; i++)
    				if(vis[trie[p][i]] == false && trie[p][i] != 0)
    					q[t++] = trie[p][i];
    			h++;
    		}
    		for( ; t >= 0 ; t--) {
    			int tmp = q[t];
    			tag[fail[tmp]] += tag[tmp];
    			dat[fail[tmp]] += tag[tmp];
    			tag[tmp] = 0;
    		}
    		return;
    	}
    }AC;
    char s[2000010];
    int n;
    int id[N];
    int main() {
    	AC.clear();
    	cin >> n;
    	for(int i = 1 ; i <= n ; i++) {
    		scanf("%s" , s);
    		id[i] = AC.insert(s);
    	}
    	scanf("%s" , s);
    	AC.build();
    	AC.article(s);
    	AC.spread();
    	for(int i = 1 ; i <= n ; i++) {
    		printf("%d
    " , AC.dat[id[i]]);
    	}
    	return 0;
    }
    

    P3796 【模板】AC自动机(加强版)

    题目

    题目描述

    (N) 个由小写字母组成的模式串以及一个文本串 (T)。每个模式串可能会在文本串中出现多次。你需要找出哪些模式串在文本串 (T) 中出现的次数最多。

    输入格式

    输入含多组数据。保证输入数据不超过 (50) 组。

    每组数据的第一行为一个正整数 (N),表示共有 (N) 个模式串,(1 leq N leq 150)

    接下去 (N) 行,每行一个长度小于等于 (70) 的模式串。下一行是一个长度小于等于 (10^6) 的文本串 (T)。保证不存在两个相同的模式串。

    输入结束标志为 (N=0)

    输出格式

    对于每组数据,第一行输出模式串最多出现的次数,接下去若干行每行输出一个出现次数最多的模式串,按输入顺序排列。

    输入输出样例

    输入 #1

    2
    aba
    bab
    ababababac
    6
    beta
    alpha
    haha
    delta
    dede
    tata
    dedeltalphahahahototatalpha
    0
    

    输出 #1

    4
    aba
    2
    alpha
    haha
    

    思路

    上面那题写出来了,这题还难吗?

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 10510
    using namespace std;
    bool vis[N];
    int q[N * 27] , h , t;
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int end[N];
    	int fail[N];
    	int tag[N];
    	int dat[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(end , 0 , sizeof(end));
    		memset(fail , 0 , sizeof(fail));
    		memset(tag , 0 , sizeof(tag));
    		memset(q , 0 , sizeof(q));
    		memset(vis , 0 , sizeof(vis));
    		memset(dat , 0 , sizeof(dat));
    		h = t = 0;
    		cnt = root;
    	}
    	int insert(char *s) {
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'a';
    			if(trie[p][c] == 0)
    				trie[p][c] = ++cnt;
    			p = trie[p][c];
    		}
    		++end[p];
    		return p;
    	}
    	void build() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)	trie[p][i] = trie[fail[p]][i];
    				else {
    					fail[trie[p][i]] = trie[fail[p]][i];
    					q.push(trie[p][i]);
    				}
    			}
    		}
    	}
    	void article(char *s) {
    		int len = strlen(s);
    		int p = root;
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'a';
    			p = trie[p][c];
    			++tag[p];
    			++dat[p];
    		}
    	}
    	void spread() {
    		
    		q[t++] = root;
    		while(h < t) {
    			vis[q[h]] = true;
    			int p = q[h];
    			for(int i = 0 ; i < 26 ; i++)
    				if(vis[trie[p][i]] == false && trie[p][i] != 0)
    					q[t++] = trie[p][i];
    			h++;
    		}
    		for( ; t >= 0 ; t--) {
    			int tmp = q[t];
    			tag[fail[tmp]] += tag[tmp];
    			dat[fail[tmp]] += tag[tmp];
    			tag[tmp] = 0;
    		}
    		return;
    	}
    }AC;
    char s[160][80];
    char txt[2000010];
    int n;
    int id[N];
    int main() {
    	while(true) {
    		memset(s , 0 , sizeof(s));
    		memset(txt , 0 , sizeof(txt));		
    		memset(id , 0 , sizeof(id));
    		AC.clear();
    		
    		scanf("%d" , &n);
    		if(n == 0)
    			break;
    		for(int i = 1 ; i <= n ; i++) {
    			scanf("%s" , s[i]);
    			id[i] = AC.insert(s[i]);
    		}
    		AC.build();
    		scanf("%s" , txt);
    		AC.article(txt);
    		AC.spread();
    		int ans = 0;
    		for(int i = 1 ; i <= n ; i++)
    			if(ans < AC.dat[id[i]])
    				ans = AC.dat[id[i]];
    		printf("%d
    " , ans);
    		for(int i = 1 ; i <= n ; i++) {
    			if(AC.dat[id[i]] == ans)
    				puts(s[i]);
    		}
    	}
    	return 0;
    }
    

    A. 【例题1】单词查询

    前三题都比较简单,不再讲解

    题目

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 1000010
    using namespace std;
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int end[N];
    	int fail[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(fail , 0 , sizeof(fail));
    		memset(end , 0 , sizeof(end));
    		cnt = 1;
    	}
    	int insert(char *s) {
    		
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int key = s[i] - 'a';
    			if(trie[p][key] == 0)
    				trie[p][key] = ++cnt;
    			p = trie[p][key];
    		}
    		end[p]++;
    		return p;
    	}
    	void kmp() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = 1;
    		q.push(root);
    		fail[root] = 0;
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)
    					trie[p][i] =  trie[fail[p]][i];
    				else {
    					q.push(trie[p][i]);
    					fail[trie[p][i]] = trie[fail[p]][i];
    				}
    			}
    		}
    	}
    	int query(char *s) {
    		int len = strlen(s);
    		int ans = 0;
    		int p = root;
    		for(int i = 0 ; i < len ; i++) {
    			int key = s[i] - 'a';
    			int k = trie[p][key];
    			while(k > 1) {
    				ans += end[k];
    				end[k] = 0;
    				k = fail[k];
    			}
    			p = trie[p][key];
    		} 
    		return ans;
    	}
    } AC;
    char s[1000010];
    int n;
    int main() {
    	int T = 1;
        cin >> T;
    	while(T--) {
    		AC.clear();
    		scanf("%d" , &n);
    		for(int i = 1 ; i <= n ; i++)
    			scanf("%s" , s) , AC.insert(s);
    		AC.kmp(); 
    		scanf("%s" , s);
    		printf("%d
    " , AC.query(s));
    	}
    	return 0;
    }
    

    B. 【例题2】单词频率

    题目

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    #include <queue>
    #define N 1000010
    using namespace std;
    bool vis[N];
    int q[N * 27] , h , t;
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int end[N];
    	int fail[N];
    	int tag[N];
    	int dat[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(end , 0 , sizeof(end));
    		memset(fail , 0 , sizeof(fail));
    		memset(tag , 0 , sizeof(tag));
    		cnt = root;
    	}
    	int insert(char *s) {
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'a';
    			if(trie[p][c] == 0)
    				trie[p][c] = ++cnt;
    			p = trie[p][c];
    		}
    		++end[p];
    		return p;
    	}
    	void build() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)	trie[p][i] = trie[fail[p]][i];
    				else {
    					fail[trie[p][i]] = trie[fail[p]][i];
    					q.push(trie[p][i]);
    				}
    			}
    		}
    	}
    	void article(char *s) {
    		int len = strlen(s);
    		int p = root;
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'a';
    			p = trie[p][c];
    			++tag[p];
    			++dat[p];
    /*			int k = p;
    			while(k > 1) {
    				++dat[k];
    				k = fail[k];
    			}//*/
    		}
    	}
    	void spread() {
    		
    		q[t++] = root;
    		while(h < t) {
    			vis[q[h]] = true;
    			int p = q[h];
    			for(int i = 0 ; i < 26 ; i++)
    				if(vis[trie[p][i]] == false && trie[p][i] != 0)
    					q[t++] = trie[p][i];
    			h++;
    		}
    		for( ; t >= 0 ; t--) {
    			int tmp = q[t];
    			tag[fail[tmp]] += tag[tmp];
    			dat[fail[tmp]] += tag[tmp];
    			tag[tmp] = 0;
    		}
    		return;
    		/*
    		vis[x] = true;
    		for(int i = 0 ; i < 26 ; i++) {
    			if(vis[trie[x][i]] == false && trie[x][i] != 0) {
    				spread(trie[x][i]);
    			}
    		}
    		tag[fail[x]] += tag[x];
    		dat[fail[x]] += tag[x];
    		tag[x] = 0;//*/
    	}
    }AC;
    char s[2000010];
    string txt;
    int n;
    int id[N];
    int main() {
    	AC.clear();
    	cin >> n;
    	for(int i = 1 ; i <= n ; i++) {
    		scanf("%s" , s);
    		id[i] = AC.insert(s);
    		AC.article(s);
    	}
    	AC.build();
    	AC.spread();
    	for(int i = 1 ; i <= n ; i++) {
    		printf("%d
    " , AC.dat[id[i]]);
    	}
    //	cout << AC.cnt << endl <<
    	return 0;
    }
    

    C. 【例题3】前缀匹配

    题目

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 1000010
    using namespace std;
    bool vis[N];
    int q[N * 27] , h , t;
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int end[N];
    	int fail[N];
    	int tag[N];
    	int dat[N];
    	int ans[N];
    	int dep[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(end , 0 , sizeof(end));
    		memset(fail , 0 , sizeof(fail));
    		memset(tag , 0 , sizeof(tag));
    		cnt = root;
    	}
    	int insert(char *s) {
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'A';
    			if(trie[p][c] == 0)
    				trie[p][c] = ++cnt;
    			dep[trie[p][c]] = dep[p] + 1,
    			p = trie[p][c];
    		}
    		end[p] = true;
    		return p;
    	}
    	void build() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)
    					trie[p][i] = trie[fail[p]][i];
    				else {
    					fail[trie[p][i]] = trie[fail[p]][i];
    					q.push(trie[p][i]);
    				}
    			}
    		}
    	}
    	void article(char *s) {
    		int len = strlen(s);
    		int p = root;
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - 'A';
    			p = trie[p][c];
    			tag[p] = true;
    			dat[p] = true;
    		}
    	}
    	void spread() {
    		
    		q[t++] = root;
    		while(h < t) {
    			vis[q[h]] = true;
    			int p = q[h];
    			for(int i = 0 ; i < 26 ; i++)
    				if(vis[trie[p][i]] == false && trie[p][i] != 0)
    					q[t++] = trie[p][i];
    			h++;
    		}
    		for( ; t >= 0 ; t--) {
    			int tmp = q[t];
    			tag[fail[tmp]] = (tag[fail[tmp]] || tag[tmp]);
    			dat[fail[tmp]] = (dat[fail[tmp]] || tag[tmp]);
    			tag[tmp] = false;
    		}
    		memset(vis , 0 , sizeof(vis));
    		dat[root] = tag[root] = 0;
    		dat[0] = 0;
    		return;
    	}
    	void GetAns(int p) {
    		vis[p] = true;
    		if(dat[p] && p > root)
    			ans[p] = dep[p];
    		for(int i = 0 ; i < 26 ; i++) {
    			int k = trie[p][i];
    			if(dep[k] <= dep[p])	continue;
    			if(!vis[k]) {
    				if(ans[k] < ans[p])
    					ans[k] = ans[p];
    				GetAns(k);
    			}
    		}
    	}
    }AC;
    char s[110];
    char txt[10000010];
    int n;
    int id[N];
    int main() {
    	AC.clear();
    	scanf("%d %d" , &n , &n);
    	scanf("%s" , txt);
    	for(int i = 1 ; i <= n ; i++) {
    		scanf("%s" , s);
    		id[i] = AC.insert(s);
    	}
    	AC.build();
    	AC.article(txt);
    	AC.spread();
    	
    	AC.GetAns(root);
    	for(int i = 1 ; i <= n ; i++)
    		printf("%d
    " , AC.ans[id[i]]);
    	return 0;
    }
    

    D. 【例题4】屏蔽词删除

    题目

    思路

    这题也不难

    把屏蔽词放到AC自动机中,记录S的每个字符在跑AC自动机时的位置,不好描述,看代码:

    	int query(ChainNode &s) {
    		int ans = 0;
    		int p = root;
    		for(int i = s.next[0] ; ~i ; i = s.next[i]) {
    			int c = s.c[i] - 'a';
    			p = trie[p][c];
    			if(end[p] > 0) {//这里下面讲
    				for(int j = 1 ; j <= dep[p] ; j++) {
    					s.del(i);
    					i = s.from[i];
    				}
    				p = s.dat[i];
    			}
    			s.dat[i] = p;//这个东西
    			
    		}
    		return ans;
    	}
    

    如果碰到结束标记就把那一串单词删掉(这里用双向链表(O(len))实现,(len)为屏蔽单词长度),从记录的位置(s.dat[i])继续跑AC自动机即可.

    S串的每个字符基本只被访问一次,最多被删除一次,所以复杂度为(O(|S|))

    code

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 100010
    #define S 1000010
    using namespace std;
    struct ChainNode {
    	int from[S] , next[S];
    	int dat[S];
    	char c[S];
    	void read() {
    		char tmp[S];
    		memset(tmp , 0 , sizeof(tmp));
    		scanf("%s" , tmp);
    		int len = strlen(tmp);
    		next[0] = 1;
    		for(int i = 1 ; i <= len ; i++)
    			c[i] = tmp[i - 1] , 
    			from[i] = i - 1 ,
    			next[i] = i + 1;
    		next[len] = -1;
    	}
    	void print() {
    		for(int i = next[0] ; ~i ; i = next[i])
    			putchar(c[i]);
    		putchar('
    ');
    	}
    	void del(int x) {//删除下标为x的字符
    		next[from[x]] = next[x];
    		from[next[x]] = from[x];
    	}
    }s;
    struct ACauto {
    	#define root 1
    	int trie[N][27];
    	int dep[N];
    	int end[N];
    	int fail[N];
    	int cnt;
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(fail , 0 , sizeof(fail));
    		memset(end , 0 , sizeof(end));
    		cnt = root;
    	}
    	int insert(char *s) {
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int key = s[i] - 'a';
    			if(trie[p][key] == 0)
    				trie[p][key] = ++cnt;
    			p = trie[p][key];
    		}
    		end[p]++;
    		dep[p] = len;
    		return p;
    	}
    	void kmp() {
    		queue <int> q;
    		for(int i = 0 ; i < 26 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		fail[root] = 0;
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i < 26 ; i++) {
    				if(trie[p][i] == 0)
    					trie[p][i] =  trie[fail[p]][i];
    				else {
    					q.push(trie[p][i]);
    					fail[trie[p][i]] = trie[fail[p]][i];
    				}
    			}
    		}
    	}
    	int query(ChainNode &s) {
    		int ans = 0;
    		int p = root;
    		for(int i = s.next[0] ; ~i ; i = s.next[i]) {
    			int c = s.c[i] - 'a';
    			p = trie[p][c];
    			if(end[p] > 0) {
    				for(int j = 1 ; j <= dep[p] ; j++) {
    					s.del(i);
    					i = s.from[i];
    				}
    				p = s.dat[i];
    			}
    			s.dat[i] = p;
    			
    		}
    		return ans;
    	}
    } AC;
    char t[100010];
    int n;
    int main() {
    	AC.clear();
    	s.read();
    	scanf("%d" , &n);
    	for(int i = 1 ; i <= n ; i++) {
    		memset(t , 0 , sizeof(t));
    		scanf("%s" , t) , AC.insert(t);
    	}
    	AC.kmp(); 
    	AC.query(s);
    	s.print();
    	return 0;
    }
    

    E. 【例题5】病毒代码

    题目

    题目描述

    二进制病毒审查委员会最近发现了如下的规律:某些确定的二进制串是病毒的代码。如果某段代码中不存在任何一段病毒代码,那么我们就称这段代码是安全的。现在委员会已经找出了所有的病毒代码段,试问,是否存在一个无限长的安全的二进制代码。

    示例:

    例如如果 ({011, 11, 00000}) 为病毒代码段,那么一个可能的无限长安全代码就是 (010101 ldots)。如果 ({01, 11, 000000}) 为病毒代码段,那么就不存在一个无限长的安全代码。

    现在给出所有的病毒代码段,判断是否存在无限长的安全代码。

    输入格式

    第一行包括一个整数 (n),表示病毒代码段的数目。

    以下的 (n) 行每一行都包括一个非空的 (01) 字符串,代表一个病毒代码段。

    输出格式

    如果存在无限长的安全代码,输出 TAK,否则输出 NIE

    输入输出样例

    输入 #1

    3
    01 
    11 
    00000
    

    输出 #1

    NIE
    

    说明/提示

    (1 leq n leq 2000),所有病毒代码段的总长度不超过 (3 imes 10^4)

    思路

    AC自动机的紫题怎么都这么简单

    一个显然的结论:

    如果001是不合法的,那么包含001的都是不合法的,如101001,00001.因此,我们求fail的时候,若001不合法(有结尾标记),则所有指向该'1'所在点的fail 所在的trie结点均打上标记.有点模糊,看代码:

    	void build() {
    		queue <int> q;
    		for(int i = 0 ; i <= 1 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i <= 1 ; i++) {
    				if(trie[p][i] == 0)
    					trie[p][i] = trie[fail[p]][i];
    				else {
    					fail[trie[p][i]] = trie[fail[p]][i];
    					tag[trie[p][i]] |= tag[trie[fail[p]][i]];//一般的AC自动机是不带这句的
    					q.push(trie[p][i]);
    				}
    			}
    		}
    	}
    

    问题就转换为AC自动机上是否存在一个环,且该环包含的点均没有被打上标记.(这一步需要对AC自动机有较深的理解)

    怎么判环?Tarjan呀!

    代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #define N 30010
    using namespace std;
    bool ans;
    int q[N * 2] , h , t;
    struct ACauto {
    #define root 1
    	int trie[N][2];
    	int fail[N];
    	bool tag[N];
    	int cnt;
    
    	int dfn[N] , low[N] , vis[N];
    	void clear() {
    		memset(trie , 0 , sizeof(trie));
    		memset(fail , 0 , sizeof(fail));
    		memset(tag , 0 , sizeof(tag));
    		memset(q , 0 , sizeof(q));
    		memset(vis , 0 , sizeof(vis));
    		h = t = 0;
    		cnt = root;
    	}
    	int insert(char *s) {
    		int p = root;
    		int len = strlen(s);
    		for(int i = 0 ; i < len ; i++) {
    			int c = s[i] - '0';
    			if(trie[p][c] == 0)
    				trie[p][c] = ++cnt;
    			p = trie[p][c];
    		}
    		tag[p] = true;
    		return p;
    	}
    	void build() {
    		queue <int> q;
    		for(int i = 0 ; i <= 1 ; i++)
    			trie[0][i] = root;
    		q.push(root);
    		while(!q.empty()) {
    			int p = q.front();
    			q.pop();
    			for(int i = 0 ; i <= 1 ; i++) {
    				if(trie[p][i] == 0)
    					trie[p][i] = trie[fail[p]][i];
    				else {
    					fail[trie[p][i]] = trie[fail[p]][i];
    					tag[trie[p][i]] |= tag[trie[fail[p]][i]];
    					q.push(trie[p][i]);
    				}
    			}
    		}
    	}
    
    	void Tarjan(int x) {
    		if(ans)	return;
    		static int Time = 1;
    		dfn[x] = low[x] = Time;
    		for(int i = 0 ; i <= 1 ; i++) {
    			int to = trie[x][i];
    			if(tag[to])	continue;
    			if(dfn[to] == 0) {
    				Tarjan(to);
    			} else if(vis[to] == 0) {
    				ans = true;//如果不能理解这里,好好学学tarjan吧
    			}
    		}
    		vis[x] = true;
    	}
    } AC;
    char s[N];
    int n;
    int main() {
    	memset(s , 0 , sizeof(s));
    	AC.clear();
    
    	scanf("%d" , &n);
    	for(int i = 1 ; i <= n ; i++) {
    		scanf("%s" , s);
    		AC.insert(s);
    	}
    	AC.build();
    	AC.Tarjan(root);
    	puts(ans ? "TAK" : "NIE");
    	return 0;
    }
    
  • 相关阅读:
    第2章 ActionScript教程 显示
    第0章 ActionScript教程 Flash相关概念
    PHP中可速查的知识点
    ActionScript3中的Int64与ByteArray转换
    第9章 ActionScript教程 应用程序国际化
    关于一个经典的委托和事件的问题 关于老鼠 猫 主人问题 帝都
    页面导入样式时,使用link和@import有什么区别
    圣杯布局和双飞翼布局的理解和区别,并用代码实现
    Mac电脑 python3.9 连接SQL Server报错
    MAC电脑如何将常规视频中音频提取出来(转换格式并调整采样频率),并利用python结合讯飞语音识别文字
  • 原文地址:https://www.cnblogs.com/dream1024/p/14786009.html
Copyright © 2011-2022 走看看