后缀自动机也是解决字符串问题的常用工具,犀利在O(N)的空间复杂度下存在给定串的后缀以及子串,而且支持在线的操作。
POJ-1509 Glass Beads
题意:求一个字符串的最小表示的开始下标。
分析:其实有一个O(N)的算法专门来解决这个问题,并且实现非常简单,不过后缀自动机同样能够解决这个问题。首先把这个串重复两次,然后从前往后一一将字符加入到后缀自动机中,最后从根开始向下遍历串的长度层即可。
#include <cstdlib> #include <cstring> #include <cstdio> #include <algorithm> using namespace std; const int N = 10005; char str[N]; struct SAM { struct Node { int ch[26]; int f, len; void init() { f = -1, len = 0; memset(ch, 0xff, sizeof (ch)); } }; Node sn[N<<1]; int idx, last; void init() { idx = last = 0; sn[idx++].init(); } int newnode() { sn[idx].init(); return idx++; } void add(int c) { int end = newnode(); int tmp = last; sn[end].len = sn[last].len + 1; for ( ; tmp != -1 && sn[tmp].ch[c] == -1; tmp = sn[tmp].f) { sn[tmp].ch[c] = end; } if (tmp == -1) sn[end].f = 0; // 所有的上一轮可接受点都没有指向字符c的孩子节点 else { int nxt = sn[tmp].ch[c]; if (sn[tmp].len + 1 == sn[nxt].len) sn[end].f = nxt; // 如果可接受点有向c的转移,且长度只加1,那么该孩子可以替代当前的end,并且end的双亲指向该孩子 else { int np = newnode(); sn[np] = sn[nxt]; sn[np].len = sn[tmp].len + 1; sn[end].f = sn[nxt].f = np; for (; tmp != -1 && sn[tmp].ch[c] == nxt; tmp = sn[tmp].f) { sn[tmp].ch[c] = np; } } } last = end; } }; SAM sam; int main() { int T; scanf("%d", &T); while (T--) { sam.init(); scanf("%s", str); int len = strlen(str); for (int i = 0; i < len*2; ++i) { sam.add(str[i%len]-'a'); } int p = 0; for (int i = 0; i < len; ++i) { for (int j = 0; j < 26; ++j) { if (sam.sn[p].ch[j] != -1) { p = sam.sn[p].ch[j]; break; } } } printf("%d\n", sam.sn[p].len-len+1); } return 0; }