最近一直在搞字符串匹配的问题,搞到AC自动机,就去找了几道题练习一下,于是就找到了ZOJ3228。
一开始看是想将所有的子串建一棵trie,然后直接AC自动机匹配,但是发现只能匹配可重复的串,那些不可重复的串搞不定(至少对于我这个弱菜来说)。
于是上网找了下别人的想法,发现很多都不是用AC自动机做的,因为根据题意,字串的长度最多为6,那么直接拿原串中所有的长度为6的前缀串构建成一棵TRIE树就可以了。
因为原串的长度为10^5,那么最多有10^5 * 6的节点。
然后就拿子串在trie树上进行前缀的匹配。
当然要分两种情况,一种是可重复的,这种就没什么好说了。直接匹配最后输出cnt就可以了。
对于不可重复的串,我们在构建TRIE的时候,多维护一个域end,来记录该子串出现的最后位置,如果当然下一个子串的首地址,也就是数组的第一个位置已经大于该子串记录的最后位置,那么更新末位置,然后计数加一。
这里我们举个例子,例如原串是"abababa"。现在我们要构建他的不可重复串的TRIE树。我这里就举LEN = 3的时候的情况。
0123456
首先位置在012的串aba,这时我们记录他的END = 2 。
下一次遍历到该串的时候是位置234,此时他的首地址等于END,那么不计数。
再下一次遍历到的时候是位置456,此时首地址大于2,那么计数+1。
同理推到LEN = 6 的情况。
#include <iostream> #include <cstdio> #include <algorithm> #include <string> #include <cmath> #include <cstring> #include <queue> #include <set> #include <vector> #include <stack> #include <map> #include <iomanip> #define PI acos(-1.0) #define Max 2505 #define inf 1<<28 #define LL(x) ( x << 1 ) #define RR(x) ( x << 1 | 1 ) #define REP(i,s,t) for( int i = ( s ) ; i <= ( t ) ; ++ i ) #define ll long long #define mem(a,b) memset(a,b,sizeof(a)) #define mp(a,b) make_pair(a,b) #define PII pair<int,int> using namespace std; inline void RD(int &ret) { char c; do { c = getchar(); } while(c < '0' || c > '9') ; ret = c - '0'; while((c=getchar()) >= '0' && c <= '9') ret = ret * 10 + ( c - '0' ); } struct trie{ int next[26] ; int overlap ; int unoverlap ; int end ; void init(){ mem(next ,0) ; overlap = 0 ;//可重复串个数 unoverlap = 0 ;//不可重复串 end = -1 ; } }TT[600005] ; int num = 0 ; void build(char *a ,int pos){ int l = strlen(a) ; int p = 0 ; for (int i = 0 ; i < l ;i ++ ){ int tt = a[i] - 'a' ; if(TT[p].next[tt] == 0){ TT[p].next[tt] = ++ num ; TT[num].init() ; } p = TT[p].next[tt] ; TT[p].overlap ++ ; if(TT[p].end < pos ){ TT[p].end = pos + i ;//记录该串最后出现的位置,当下次出现位置已经大于该位置时,则可以计数 TT[p].unoverlap ++ ; } } } void search(char *a ,int pos){ int l = strlen(a) ; int p = 0 ; for (int i = 0 ; i < l ;i ++ ){ int tt = a[i] - 'a' ; if(TT[p].next[tt] == 0){ puts("0") ; return ; } p = TT[p].next[tt] ; } if(pos){ printf("%d\n",TT[p].unoverlap) ; }else { printf("%d\n",TT[p].overlap) ; } return ; } char str[1111111] ; void init(){ num = 0 ; TT[0].init() ; } char now[111] ; int main() { int cas = 0 ; while(scanf("%s",str) != EOF){ init() ; int l = strlen(str) ; for (int i = 0 ;i < l ; ++ i){ int j ; for (j = 0 ; j < 6 && i + j < l ; j ++ ){//枚举所有长度为6的前缀串,加入TRIE树中。 now[j] = str[i + j] ; } now[j] = 0 ; build(now , i) ; } int n ; RD(n) ; int op ; printf("Case %d\n",++ cas) ; while(n -- ){ scanf("%d%s",&op ,now) ; search(now ,op) ; } puts("") ; } return 0 ; }目测还有AC自动机的做法,我去学习一下。