[SDOI2016]生成魔咒 后缀自动机
题面:洛谷
题解:
题意:给定串,对于每个前缀求有多少本质不同的子串。
我们对这个串建后缀自动机,每加入一个字符就更新一次答案,更新一次输出一次。
对于后缀自动机上的每个状态,因为以[MINS, MAXS]中的每个值作为长度都可以表示一个不同的子串,所以这个状态的贡献就是这个区间的长度。
因此建的时候更新ans即可
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define LL long long 4 #define R register int 5 #define AC 201000 6 7 LL ans; 8 struct sam_atm{ 9 int last, cnt; 10 map<int, int> ch[AC];//因为任意一个字符都可能是一个超大的数字,所以要用map 11 int fa[AC], l[AC]; 12 13 inline void add(int c) 14 { 15 int p = last, np = ++ cnt; 16 last = np, l[np] = l[p] + 1; 17 for( ; p && !ch[p][c]; p = fa[p]) ch[p][c] = np;//对于没有这条边的点,加上这条边 18 if(!p) fa[np] = 1, ans += l[np] - l[1];//加上贡献 19 else 20 { 21 int q = ch[p][c];//找到第一个有c这条边的节点的这条边对应的状态 22 if(l[p] + 1 == l[q]) fa[np] = q, ans += l[np] - l[q];//如果不会产生影响,就直接接上去 23 else 24 { 25 int nq = ++ cnt;//再新建一个节点 26 l[nq] = l[p] + 1;//长度+1 27 ch[nq] = ch[q], ans -= l[q] - l[fa[q]];//这是map自带的功能??? 28 //memcpy(ch[nq], ch[q], sizeof(ch[q]));//把对应状态的所有边都赋值到新点上 29 fa[nq] = fa[q], ans += l[nq] - l[fa[nq]];//把新点连上去,把q拉下来,因为q的位置被改变了,所以要重新统计它的贡献,即删去原来的,加上新的 30 fa[q] = fa[np] = nq, ans += l[q] + l[np] - (l[nq] << 1);//把这2个点都放在下面 31 for( ; ch[p][c] == q; p = fa[p]) ch[p][c] = nq; 32 }//因为这里没有涉及fa的改变,所以[Min(s), Max(s)]也没有发生改变,无需修改ans 33 } 34 } 35 36 }sam; 37 38 inline int read() 39 { 40 int x = 0;char c = getchar(); 41 while(c > '9' || c < '0') c = getchar(); 42 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 43 return x; 44 } 45 46 void work() 47 { 48 int n = read(); 49 sam.last = sam.cnt = 1;//记得初始化! 50 for(R i = 1; i <= n; i ++) 51 { 52 sam.add(read()); 53 printf("%lld\n", ans); 54 } 55 } 56 57 int main() 58 { 59 // freopen("in.in", "r", stdin); 60 work(); 61 // fclose(stdin); 62 return 0; 63 }