来介绍一些基本操作
首先,介绍一下 Suffix Automaton
后缀自动机大概由两部分组成—— DAWG 和 Parent Tree
1.DAWG
DAWG 的中文名字叫做“单词的有向无环图”
它由一个初始节点 init ,若干条转移边,若干个节点组成
DAWG 表示的是状态的转移关系,我们可以记一个点能识别的终止位置集合为 $end-pos(i)$,每个点的子串是一个前缀的一些后缀,这些后缀的长度都在 [minlen,maxlen] 这个区间里
2.Parent Tree
Parent Tree 类似 AC 自动机的 fail 树,是由 $end-pos$ 集合的包含关系构成的一棵树,满足 fa[i] 的 maxlen + 1 等于 i 的 minlen
由这个我们可以知道对 DAWG 拓扑排序相当于对 maxlen 数组快速排序/基数排序,由这个我们也可以知道其实并不用记录每个点的 minlen
Parent Tree 是反串的后缀树
由此我们可以做题
bzoj3879 SvT
给你一个串和若干组询问,每组询问包括若干个后缀,你要求出这些后缀两两间最长公共前缀长度的和
sol:后缀 i 和后缀 j 的 lcp 相当于后缀树上 i 的位置和 j 的位置的 LCA 深度
我们把串反过来,然后建 SAM
然后我们虚树 + 树形 dp 就可以了
#include<bits/stdc++.h> #define LL long long using namespace std; inline int read() { int x = 0,f = 1;char ch = getchar(); for(;!isdigit(ch);ch = getchar())if(ch == '-')f = -f; for(;isdigit(ch);ch = getchar())x = 10 * x + ch - '0'; return x * f; } const int maxn = 1200010; const LL mod = 23333333333333333LL; int n,a[maxn],pos[maxn],rnk[maxn]; int tr[maxn][26]; int fa[maxn],len[maxn],dfn,root,last; char s[maxn]; void extend(int c) { int p = last,np = last = ++dfn; len[np] = len[p] + 1; while(p && !tr[p][c])tr[p][c] = np,p = fa[p]; if(!p)fa[np] = root; else { int q = tr[p][c]; if(len[q] == len[p] + 1)fa[np] = q; else { int nq = ++dfn; len[nq] = len[p] + 1;memcpy(tr[nq],tr[q],sizeof(tr[nq]));fa[nq] = fa[q],fa[np] = fa[q] = nq; while(p && tr[p][c] == q)tr[p][c] = nq,p = fa[p]; } } } int first[maxn],to[maxn],nx[maxn],cnt; LL ans; int val[maxn]; inline void add(int u,int v){to[++cnt] = v;nx[cnt] = first[u];first[u] = cnt;} inline void ins(int u,int v){add(u,v);add(v,u);} int size[maxn],dep[maxn],ff[maxn],bl[maxn],ind[maxn],_tim; inline void dfs1(int x) { size[x] = 1;ind[x] = ++_tim; for(int i=first[x];i;i=nx[i]) { if(to[i] == ff[x])continue; ff[to[i]] = x; dep[to[i]] = dep[x] + 1; dfs1(to[i]); size[x] += size[to[i]]; } } inline void dfs2(int x,int col) { bl[x] = col; int k = 0; for(int i=first[x];i;i=nx[i]) if(to[i] != ff[x] && size[to[i]] > size[k])k = to[i]; if(!k)return; dfs2(k,col); for(int i=first[x];i;i=nx[i]) if(to[i] != ff[x] && to[i] != k)dfs2(to[i],to[i]); } inline int lca(int x,int y) { while(bl[x] != bl[y]) { if(dep[bl[x]] < dep[bl[y]])swap(x,y); x = ff[bl[x]]; }return dep[y] < dep[x] ? y : x; } inline bool cmp(const int &x,const int &y){return ind[x] < ind[y];} int stk[maxn],f[maxn]; inline void dp(int x) { f[x] = val[x] ? 1 : 0; for(int i=first[x];i;i=nx[i]) { dp(to[i]); ans += (LL)f[x] * f[to[i]] * len[x]; f[x] += f[to[i]]; } first[x] = 0; //cout<<x<<endl; } int main() { #ifdef Ez3real freopen("ww.in","r",stdin); #endif root = last = ++dfn; n = read();int q = read();scanf("%s",s + 1); reverse(s + 1,s + n + 1); for(int i=1;i<=n;i++)extend(s[i] - 'a'),pos[n - i + 1] = last; for(int i=1;i<=dfn;i++)add(fa[i],i);dfs1(root);dfs2(root,root); memset(first,0,sizeof(first)); while(q--) { cnt = 0; int k = read();//cout<<k<<"!!"<<endl; for(int i=1;i<=k;i++)a[i] = pos[read()]; sort(a + 1,a + k + 1,cmp); int nn = 0;a[++nn] = a[1]; for(int i=2;i<=k;i++) if(a[i] != a[i - 1])a[++nn] = a[i]; for(int i=1;i<=nn;i++)val[a[i]] = 1; int top = 0; for(int i=1;i<=nn;i++) { if(!top){stk[++top] = a[i];continue;} int x = a[i],l = lca(x,stk[top]); while(ind[l] < ind[stk[top]]) { if(ind[l] >= ind[stk[top - 1]]) { add(l,stk[top--]); if(l != stk[top])stk[++top] = l; break; }else add(stk[top - 1],stk[top]),top--; } stk[++top] = x; } while(top > 1)add(stk[top - 1],stk[top]),top--; ans = 0; dp(stk[1]); printf("%lld\n",ans); for(int i=1;i<=nn;i++)val[a[i]] = 0; } }