P1368 工艺
把串插入 S A M SAM SAM 插两次,然后贪心找最小字典序即可
[AHOI2013]差异
两个串的最长公共后缀就是后缀自动机上 lca 的长度
于是把两个串反过来,对于每一个结点统计有多少对结点以它为 lca,size 乘一下就可以了
[TJOI2015]弦论
对于后缀自动机的 DAG,求出 s u m [ i ] sum[i] sum[i] 表示 i 往后走能走多少个字符串
如果不同位置算不同,那么令 s i z i siz_i sizi​ 为 e n d p o s endpos endpos 集合的大小
如果算相同,那么令 s i z i siz_i sizi​ 为 1 就好了
[SDOI2016]生成魔咒
正好 S A M SAM SAM可以增量构造,考虑加一个字符的贡献,就是这个点的 m x l e n − m i l e n + 1 mxlen - milen+1 mxlen−milen+1

SPOJ LCS1:一个建 SAM,另一个丢上去跑就好了
SPOJ LCS2:一个建 SAM,其他每个都丢上去跑,记录 m x [ u ] mx[u] mx[u] 表示后缀自动机 u 结点能匹配的最大长度
然后求 m i n ( m x [ u ] ) min(mx[u]) min(mx[u]),等等,当前结点能作为答案,那么 l i n k [ u ] link[u] link[u] 一定可以被走到,而且可以走完
于是倒过来求答案,如果当前可以走到,那么将 m x [ l i n k [ u ] ] mx[link[u]] mx[link[u]] 设为 l e n [ u ] len[u] len[u] 就可以了
BZOJ1396 识别子串
建出 S A M SAM SAM, ∣ R i g h t ∣ |Right| ∣Right∣ 为 1 的就是一个识别子串
这些合法的串的在原串的位置是 [ 1 , 2... l e n [ x ] − l e n [ l i n k [ x ] ] , l e n [ x ] ] [1,2...len[x]-len[link[x]] , len[x]] [1,2...len[x]−len[link[x]],len[x]]
考虑这个可以作为哪些位置的识别子串
对于 [ 1 , l e n [ x ] − l e n [ l i n k [ x ] ] [1,len[x]-len[link[x]] [1,len[x]−len[link[x]],以它为答案的识别子串长度为 l e n [ x ] − i + 1 len[x]-i+1 len[x]−i+1
对于 [ l e n [ x ] − l e n [ l i n k [ x ] ] , l e n [ x ] ] [len[x]-len[link[x]],len[x]] [len[x]−len[link[x]],len[x]],以它为答案的识别子串的长度为 l e n [ x ] − l e n [ l i n k [ x ] ] len[x]-len[link[x]] len[x]−len[link[x]]
用两棵线段树维护即可
[TJOI2016]字符串
后缀数组做法:二分答案,那么 m i d ≤ h e i g h t mid\le height mid≤height 对应一段区间,可以二分出来
然后看一下这段区间 [ l , r ] [l,r] [l,r] 内有没有在区间 [ a , b − m i d ] [a,b-mid] [a,b−mid] 里面的,用主席树即可
SAM做法:先要把串倒过来
二分答案,找到 [ c , c + m i d − 1 ] [c,c+mid-1] [c,c+mid−1] 所在的结点,查询它 ∣ R i g h t ∣ |Right| ∣Right∣ 集合中有无在区间 [ a + m i d − 1 , b ] [a+mid-1,b] [a+mid−1,b] 内的结点,写一个线段树合并或者主席树即可
[HAOI2016]找相同字符
题解:传送门
对第一个串建一个SAM, 拿第二个上去跑
考虑跑到一个点的贡献, 就是(当前匹配的长度 nowlen - right 集合的最短长度 minlen) * right 集合的大小
因为跟每个长度在这之间的串都可以匹配 siz 次
然后跳 link, 对于它上方的点的贡献, 就是 (maxlen - minlen + 1) * siz
所以我们记录每个点被考虑的次数, 最后向前统计一遍即可
[TJOI2017]DNA
hash 暴力匹配,每次匹配最多跳 3 次
如果怕被卡可以将两个串接在一起做后缀数组求一个 l c p lcp lcp 加速一下匹配
BZOJ3756 Pty的字符串
建出广义后缀自动机,然后把串放上去跑
跑到一个点的贡献是 ( l e n − m i n l e n ) ∗ ∣ r i g h t ∣ (len-minlen)*|right| (len−minlen)∗∣right∣,然后对于祖先的点,有 ∣ r i g h t ∣ ∗ ( m a x l e n − m i n l e n ) |right|*(maxlen-minlen) ∣right∣∗(maxlen−minlen),用一个前缀和之类的东西记录当前点到根路径上的 ∣ r i g h t ∣ ∗ ( m a x l e n − m i n l e n ) |right|*(maxlen-minlen) ∣right∣∗(maxlen−minlen) 的和
关于广义 SAM:只需要将 l a s t last last 改成父亲节点在 S A M SAM SAM 上的对应节点即可

#include<bits/stdc++.h>
#define N 2000050
using namespace std;
int n, la[N], las, node;
int ch[N][3], link[N], len[N], siz[N];
int c[N], a[N];
typedef long long ll;
ll f[N]; char S[N];
void extend(int p, int c){if(ch[p][c]){int q = ch[p][c];if(len[q] == len[p] + 1){ ++siz[q]; las = q; return;}int clone = ++node; len[clone] = len[p] + 1; siz[clone] = 1; for(int i = 0; i < 3; i++) ch[clone][i] = ch[q][i];link[clone] = link[q]; link[q] = clone;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;las = clone;}else{int now = ++node; len[now] = len[p] + 1; siz[now] = 1; for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;if(p == 0) link[now] = 1;else{int q = ch[p][c];if(len[q] == len[p] + 1) link[now] = q;else{int clone = ++node; len[clone] = len[p] + 1;for(int i = 0; i < 3; i++) ch[clone][i] = ch[q][i];link[clone] = link[q]; link[q] = link[now] = clone;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;}} las = now;}
}
int main(){las = node = la[1] = 1;scanf("%d", &n); for(int i = 2; i <= n; i++){int x; char c[3]; scanf("%d%s", &x, c);extend(la[x], c[0] - 'a'); la[i] = las;}for(int i = 1; i <= node; i++) c[len[i]]++;for(int i = 1; i <= n; i++) c[i] += c[i-1];for(int i = node; i >= 1; i--) a[c[len[i]]--] = i;for(int i = node; i >= 1; i--){ int now = a[i]; siz[link[now]] += siz[now]; }for(int i = 1; i <= node; i++) f[i] = 1ll * (len[i] - len[link[i]]) * siz[i];for(int i = 1; i <= node; i++){ int now = a[i]; f[now] += f[link[now]];}scanf("%s", S + 1); int l = strlen(S + 1), nowlen = 0, now = 1;ll ans = 0;for(int i = 1; i <= l; i++){int c = S[i] - 'a';while(now && !ch[now][c]) now = link[now], nowlen = len[now];if(!now) now = 1, nowlen = 0;else nowlen++, now = ch[now][c], ans += 1ll * (nowlen - len[link[now]]) * siz[now] + f[link[now]];} cout << ans; return 0;
}

BZOJ 4310 跳蚤
首先可以二分答案,然后求出第 k 大的子串的起始位置和末位置
从后往前扫一遍,每次比较两个串的大小,如果不行就切开
比较大小可以用后缀数组求 l c p lcp lcp
另外本质不同的串的个数是 ∑ n − s a i + 1 − h e i g h t i \sum n - sa_i+1-height_i ∑n−sai​+1−heighti​

#include<bits/stdc++.h>
#define N 100050
using namespace std;
int sa[N], rk[N], c[N], tp[N], y[N], hi[N];
int st[N][20], lg[N];
int k, n, m; char S[N];
typedef long long ll;
int ls, rs;
void Sort(){for(int i = 0; i <= m; i++) c[i] = 0;for(int i = 1; i <= n; i++) c[rk[i]]++;for(int i = 1; i <= m; i++) c[i] += c[i-1];for(int i = n; i >= 1; i--) sa[c[rk[y[i]]]--] = y[i];
}
void SA(){for(int i = 1; i <= n; i++) rk[i] = S[i], y[i] = i; Sort();for(int k = 1; k <= n; k <<= 1){int ret = 0;for(int i = n - k + 1; i <= n; i++) y[++ret] = i;for(int i = 1; i <= n; i++) if(sa[i] > k) y[++ret] = sa[i] - k;Sort(); swap(rk, tp); rk[sa[1]] = 1; int num = 1;for(int i = 2; i <= n; i++){if(tp[sa[i]] == tp[sa[i-1]] && tp[sa[i]+k] == tp[sa[i-1]+k])rk[sa[i]] = num;else rk[sa[i]] = ++num;} m = num;}
}
void Hi(){int k = 0;for(int i = 1; i <= n; i++){if(rk[i] == 1) continue;int j = sa[rk[i]-1]; if(k) k--;while(i+k <= n && j+k <= n && S[i+k] == S[j+k]) ++k;hi[rk[i]] = k;}for(int i = 1; i <= n; i++) st[i][0] = hi[i];for(int i = 1; (1 << i) <= n; i++)for(int j = 1; j + (1 << i) - 1 <= n; j++)st[j][i] = min(st[j][i-1], st[j + (1<<(i-1))][i-1]);
}
void FSY(ll k){for(int i = 1; i <= n; i++){ll now = n - sa[i] + 1 - hi[i];if(now < k) k -= now;else{ ls = sa[i]; rs = sa[i] + hi[i] + k - 1; break;}}
}
int lcp(int x, int y){if(x == y) return n - x + 1;int l = min(rk[x], rk[y]) + 1, r = max(rk[x], rk[y]), k = lg[r - l + 1];return min(st[l][k], st[r-(1<<k)+1][k]);
}
bool cmp(int l1, int r1, int l2, int r2){int len1 = r1 - l1 + 1, len2 = r2 - l2 + 1, lc = lcp(l1, l2);if(lc >= len2 && len1 > len2) return true;if(lc >= len1 && len2 >= len1) return false;if(lc >= len1 && lc >= len2) return len1 > len2;return S[l1 + lc] > S[l2 + lc];
}
bool check(ll w){FSY(w);int cnt = 1, las = n;for(int i = n; i >= 1; i--){if(S[i] > S[ls]) return false;if(cmp(i, las, ls, rs)) ++cnt, las = i;if(cnt > k) return false;} return true;
}
int main(){scanf("%d", &k);scanf("%s", S + 1); n = strlen(S + 1); m = 127;for(int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;SA(); Hi(); ll l = 1, r = 0;for(int i = 1; i <= n; i++) r += (ll)(n - sa[i] + 1 - hi[i]);while(l < r){ll mid = (l+r) >> 1;if(check(mid)) r = mid; else l = mid + 1;} FSY(l); for(int i = ls; i <= rs; i++) cout << S[i]; return 0;
}

BZOJ 3413 匹配
题意:问 O ( n m ) O(nm) O(nm) 的暴力匹配要匹配多少次
首先答案 = = = 匹配次数 + 失配次数
分两种情况讨论:
1.匹配完了就跑了
2.在原串中没有出现
只需要对原串建 S A M SAM SAM 然后判一下出现没有即可
假设出现过并且结尾位置为 p p p
那么答案就是 [ 1 , p − l e n + 1 ] [1,p-len+1] [1,p−len+1] 中的每一个 i i i 的后缀与匹配串的 l c p + 1 lcp+1 lcp+1
如果枚举 i 并暴力查的话要凉
然后就有一个很骚的 t r i c k trick trick
我们可以枚举 l c p lcp lcp,并查每种 l c p lcp lcp 出现的次数加起来
显然一个长度为 l e n len len 的 l c p lcp lcp 会在 1 , 2... l e n 1,2...len 1,2...len 加 l e n len len 次
然后就可以拿匹配串在原串上跑,查询有多少 e n d p o s endpos endpos在 [ 1 , p − l e n + j ] [1,p-len+j] [1,p−len+j],j 为当前已经匹配的长度
如果没有出现的话,把 p − l e n + j p-len+j p−len+j 的上限设成 n n n 即可
然后没有出现的失配次数是 n n n,出现过的失配次数是 p − l e n p-len p−len

#include<bits/stdc++.h>
#define N 1000050
using namespace std;
typedef long long ll;
int n, m; char S[N];
int las, node;
int ch[N][10], link[N], len[N];
int a[N], c[N], mi[N], ed[N];
int rt[N], ls[N << 5], rs[N << 5], sum[N << 5];
struct Segmentree{int node;void ins(int &x, int l, int r, int p){if(!x) x = ++node; ++sum[x];if(l == r) return; int mid = (l+r) >> 1;if(p <= mid) ins(ls[x], l, mid, p);else ins(rs[x], mid+1, r, p);}int merge(int x, int y){if(!x || !y) return x + y;int nxt = ++node; sum[nxt] = sum[x] + sum[y];ls[nxt] = merge(ls[x], ls[y]);rs[nxt] = merge(rs[x], rs[y]); return nxt;}int query(int x, int l, int r, int L, int R){if(!x) return 0;if(L<=l && r<=R) return sum[x];int mid = (l+r) >> 1, ans = 0;if(L<=mid) ans += query(ls[x], l, mid, L, R);if(R>mid) ans += query(rs[x], mid+1, r, L, R);return ans;}
}Seg;
int extend(int id, int c){int now = ++node, p = las; len[now] = len[p] + 1;for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;Seg.ins(rt[now], 1, n, id); ed[now] = id;if(p == 0) link[now] = 1;else{int q = ch[p][c];if(len[q] == len[p] + 1) link[now] = q;else{int cl = ++node; len[cl] = len[p] + 1;link[cl] = link[q];for(int i = 0; i < 10; i++) ch[cl][i] = ch[q][i];link[q] = link[now] = cl;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = cl;}} las = now;
}
void FSY(int len, int &lim){int now = 1;for(int i = 1; i <= len; i++){int c = S[i] - '0';if(!ch[now][c]) return; now = ch[now][c];} lim = mi[now];
}
int main(){scanf("%d%s", &n, S + 1); las = node = 1;for(int i = 1; i <= n; i++) extend(i, S[i] - '0');for(int i = 1; i <= node; i++) mi[i] = n + 1;for(int i = 1; i <= node; i++) c[len[i]]++;for(int i = 1; i <= n; i++) c[i] += c[i-1];for(int i = node; i >= 1; i--) a[c[len[i]]--] = i;for(int i = node; i >= 1; i--){int x = a[i]; if(ed[x]) mi[x] = min(mi[x], ed[x]);mi[link[x]] = min(mi[link[x]], mi[x]);rt[link[x]] = Seg.merge(rt[link[x]], rt[x]);}scanf("%d", &m);for(int i = 1; i <= m; i++){scanf("%s", S + 1);int len = strlen(S + 1);int lim = n + 1;FSY(len, lim);ll ans = 0; if(lim != n+1) ans = lim - len;else ans = n; // failureint now = 1;for(int j = 1; j <= len; j++){int c = S[j] - '0';now = ch[now][c];if(!now) break;ans += (ll)Seg.query(rt[now], 1, n, 1, (lim == n+1 ? n : lim - len + j));} cout << ans << '\n';} return 0;
}

[CTSC2012]熟悉的文章
首先可以二分答案然后 check
设 m x i mx_i mxi​ 为以 i 结尾的串与文库的最大匹配长度
f i f_{i} fi​ 表示到 i 的最大匹配长度
f i = m a x ( f j + i − ( j + 1 ) + 1 ) ( j ∈ [ i − m x i , i − l e n ] ] f_i=max(f_j+i-(j+1)+1)(j\in [i-mx_i,i-len]] fi​=max(fj​+i−(j+1)+1)(j∈[i−mxi​,i−len]]
l e n len len 为当前二分的长度,发现 i − m x i i-mx_i i−mxi​ 单调不减,可以单调队列优化
然后对于文库建出广义 S A M SAM SAM 匹配一下即可

#include<bits/stdc++.h>
#define N 1100050
using namespace std;
int ch[N << 1][2], link[N << 1], len[N << 1];
int n, m;
char S[N];
int las, node;
int mx[N];
void extend(int c){int now = ++node, p = las; for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;if(!p) link[now] = 1;else{int q = ch[p][c];if(len[q] == len[p] + 1) link[now] = q;else{int cl = ++node; len[cl] = len[p] + 1;link[cl] = link[q];for(int i = 0; i < 2; i++) ch[cl][i] = ch[q][i];link[q] = link[now] = cl;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = cl;}} las = now;
}
int q[N], l, r, f[N];
bool check(int n, int L){l = 1; r = 0; for(int i = 0; i <= n; i++) f[i] = 0; for(int i = L; i <= n; i++){while(l <= r && f[q[r]] - q[r] < f[i - L] - (i - L)) r--;q[++r] = i - L;while(l <= r && q[l] < i - mx[i]) l++;f[i] = f[i-1];if(l <= r) f[i] = max(f[i], f[q[l]] + i - q[l]);} return f[n] * 10 >= n * 9;
}
int main(){node = 1;scanf("%d%d", &n, &m);for(int i = 1; i <= m; i++){scanf("%s", S); int len = strlen(S);las = 1; for(int j = 0; j < len; j++) extend(S[j] - '0');}for(int i = 1; i <= n; i++){scanf("%s", S + 1); int L = strlen(S + 1);int now = 1, nowlen = 0;for(int j = 1; j <= L; j++){int c = S[j] - '0';while(now && !ch[now][c]) now = link[now], nowlen = len[now];if(!now) now = 1, nowlen = 0;else now = ch[now][c], ++nowlen; mx[j] = nowlen;} int l = 0, r = L;while(l < r){int mid = (l+r+1) >> 1;if(check(L, mid)) l = mid;else r = mid - 1;} cout << l << '\n';} return 0;
}

BZOJ 3879 SvT
SA:按 r a n k rank rank 排序,把相邻的 l c p lcp lcp 取出来,一个数的贡献是它乘以它作为最小值的区间个数
用单调栈即可
SAM:两个串的 l c p lcp lcp 是后缀树上的 l c a lca lca,把虚树建出来统计 l c a lca lca 的贡献即可

#include<bits/stdc++.h>
#define N 500050
using namespace std;
int read(){int cnt = 0, f = 1; char ch = 0;while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();return cnt * f;
}
typedef long long ll;
const ll Mod = 23333333333333333ll;
int n, m, q; char S[N];
int sa[N], rk[N], y[N], c[N], tp[N], hi[N], st[N][22], lg[N];
void Sort(){for(int i = 0; i <= m; i++) c[i] = 0;for(int i = 1; i <= n; i++) c[rk[i]]++;for(int i = 1; i <= m; i++) c[i] += c[i-1];for(int i = n; i >= 1; i--) sa[c[rk[y[i]]]--] = y[i];
}
void SA(){for(int i = 1; i <= n; i++) rk[i] = S[i], y[i] = i; Sort();for(int k = 1; k <= n; k <<= 1){int ret = 0;for(int i = n - k + 1; i <= n; i++) y[++ret] = i;for(int i = 1; i <= n; i++) if(sa[i] > k) y[++ret] = sa[i] - k;Sort(); swap(rk, tp); rk[sa[1]] = 1; int num = 1;for(int i = 2; i <= n; i++){if(tp[sa[i]] == tp[sa[i-1]] && tp[sa[i] + k] == tp[sa[i-1] + k])rk[sa[i]] = num;else rk[sa[i]] = ++num;} m = num;}
}
void Hi(){int k = 0;for(int i = 1; i <= n; i++){if(rk[i] == 1) continue;int j = sa[rk[i] - 1]; if(k) k--;while(j + k <= n && i + k <= n && S[j + k] == S[i + k]) k++;hi[rk[i]] = k;}for(int i = 1; i <= n; i++) st[i][0] = hi[i];for(int i = 1; (1<<i) <= n; i++) for(int j = 1; j + (1<<i) - 1 <= n; j++)st[j][i] = min(st[j][i-1], st[j + (1<<(i-1))][i-1]);for(int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
}
int a[N], b[N], sta[N], l[N], r[N];
int RMQ(int l, int r){ int x = lg[r - l + 1];return min(st[l][x], st[r-(1<<x)+1][x]);
}
int main(){scanf("%d%d%s", &n, &q, S + 1); m = 127;SA(); Hi();while(q--){int k = read(); for(int i = 1; i <= k; i++) a[i] = rk[read()]; sort(a + 1, a + k + 1);k = unique(a + 1, a + k + 1) - (a + 1);for(int i = 1; i < k; i++) b[i] = RMQ(a[i] + 1, a[i + 1]);int tp = 0;b[0] = b[k] = -1; sta[++tp] = 0;for(int i = 1; i < k; i++){while(tp && b[sta[tp]] > b[i]) tp--;l[i] = sta[tp]; sta[++tp] = i;} tp = 1; sta[tp] = k;for(int i = k - 1; i >= 1; i--){while(tp && b[sta[tp]] >= b[i]) tp--;r[i] = sta[tp]; sta[++tp] = i;}ll ans = 0;for(int i = 1; i < k; i++) ans = (ans + 1ll * (i - l[i]) * (r[i] - i) * b[i]) % Mod;cout << ans << '\n';}
}

[ZJOI2015]诸神眷顾的幻想乡
考虑到叶子结点只有不超过 20 个,而一条路径仅在以一个叶子为根的树中为从上到下的路径
于是从每个叶子开始做一次广义后缀自动机,一个结点的贡献是 l e n i − l e n l i n k i len_i - len_{link_i} leni​−lenlinki​​

#include<bits/stdc++.h>
#define N 200050
using namespace std;
typedef long long ll;
int read(){int cnt = 0, f = 1; char ch = 0;while(!isdigit(ch)){ ch = getchar(); if(ch == '-') f = -1;}while(isdigit(ch)) cnt = cnt*10 + (ch-'0'), ch = getchar();return cnt * f;
}
int first[N], nxt[N], to[N], du[N], tot;
void add(int x, int y){ nxt[++tot] = first[x], first[x] = tot, to[tot] = y; ++du[x]; }
int n, C;
int col[N];
int ch[N * 20][12], link[N * 20], len[N * 20];
int node;
int extend(int c, int p){if(ch[p][c]){int q = ch[p][c]; if(len[q] == len[p] + 1) return q;int clone = ++node; len[clone] = len[p] + 1;for(int i = 0; i <= C; i++) ch[clone][i] = ch[q][i];link[clone] = link[q]; link[q] = clone;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;return clone;}else{int now = ++node; len[now] = len[p] + 1;for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;if(p == 0) link[now] = 1;else{int q = ch[p][c]; if(len[q] == len[p] + 1) link[now] = q;else{int clone = ++node; len[clone] = len[p] + 1;link[clone] = link[q];for(int i = 0; i <= C; i++) ch[clone][i] = ch[q][i];link[q] = link[now] = clone; for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;}} return now;}
}
void dfs(int u, int fa, int las){int p = extend(col[u], las);for(int i = first[u]; i; i = nxt[i]){int t = to[i]; if(t == fa) continue;dfs(t, u, p);}
}
int main(){n = read(); C = read();for(int i = 1; i <= n; i++) col[i] = read();for(int i = 1; i < n; i++){int x = read(), y = read();add(x, y); add(y, x);} node = 1;for(int i = 1; i <= n; i++) if(du[i] == 1) dfs(i, 0, 1);ll ans = 0;for(int i = 2; i <= node; i++) ans += (ll)len[i] - (ll)len[link[i]];cout << ans; return 0;
}

BZOJ 5084 hashit
考虑到本质不同的子串数为
∑ n − s a [ i ] + 1 − h e i g h t [ i ] \sum n - sa[i]+1-height[i] ∑n−sa[i]+1−height[i]
需要动态维护 s a , r a n k sa, rank sa,rank
把串倒过来插就是后缀平衡树的模板
插入或删除过后需要重新获得 h e i g h t height height,二分+ h a s h hash hash 求一个 l c p lcp lcp 就好

#include<bits/stdc++.h>
#define N 100050
using namespace std;
typedef long long ll;
const ll inf = 1e18;
typedef unsigned long long ull;
int Rnd(){ return rand() | (rand() << 15);}
char s[N];
int hi[N], rnd[N];
int rt, ch[N][2]; ll val[N];
int n, len, siz[N]; ll ans;
const ull Base = 5261023;
ull hash[N], pw[N];void pia(int x, ll l, ll r){if(!x) return; ll mid = l + r >> 1;val[x] = mid; pia(ch[x][0], l, mid-1); pia(ch[x][1], mid+1, r);siz[x] = siz[ch[x][0]] + siz[ch[x][1]] + 1;
}
void rot(int &x, int y, ll l, ll r){return;int k = ch[x][1] == y; ch[x][k] = ch[y][k^1]; ch[y][k^1] = x; x = y; pia(x, l, r);
}
bool cmp(int x, int y){ return s[x] < s[y] || (s[x] == s[y] && val[x - 1] < val[y - 1]);}
void Add(int &x, ll l, ll r){if(!x){ x = len; val[x] = (l + r) >> 1; siz[x] = 1; rnd[x] = Rnd();ch[x][0] = ch[x][1] = 0; return;} ll mid = l + r >> 1; ++siz[x];if(cmp(len, x)){ Add(ch[x][0], l, mid-1); if(rnd[x] > rnd[ch[x][0]]) rot(x, ch[x][0], l, r); }else { Add(ch[x][1], mid+1, r); if(rnd[x] > rnd[ch[x][1]]) rot(x, ch[x][1], l, r); }
}
int rk(int x, int k){if(x == k) return siz[ch[x][0]] + 1;if(cmp(k, x)) return rk(ch[x][0], k);else return rk(ch[x][1], k) + siz[ch[x][0]] + 1;
}
int kth(int x, int k){if(!x) return 0;if(siz[ch[x][0]] >= k) return kth(ch[x][0], k);else if(siz[ch[x][0]] + 1 == k) return x;else return kth(ch[x][1], k - siz[ch[x][0]] - 1);
}
bool ck(int l1, int l2, int len){return (hash[l1] - hash[l1 - len] * pw[len]) == (hash[l2] - hash[l2 - len] * pw[len]);
}
int lcp(int x, int y){int l = 0, r = min(x, y);while(l < r){int mid = (l+r+1) >> 1;if(ck(x, y, mid)) l = mid;else r = mid - 1;} return l;
}
void ins(int k){s[++len] = s[k]; hash[len] = hash[len - 1] * Base + s[k];Add(rt, 1, inf);int a = rk(rt, len), b = kth(rt, a - 1), c = kth(rt, a + 1);if(c) ans -= hi[c]; if(c) hi[c] = lcp(len, c); if(b) hi[len] = lcp(b, len);ans += hi[len] + hi[c];
}
int merge(int x, int y){if(!x || !y) return x + y;if(rnd[x] < rnd[y]){ ch[x][1] = merge(ch[x][1], y); return x;}else { ch[y][0] = merge(x, ch[y][0]); return y;}
}
void Del(int &x, ll l, ll r){if(x == len){x = merge(ch[x][0], ch[x][1]);pia(x, l, r); return;}--siz[x]; ll mid = l + r >> 1;if(cmp(len, x)) Del(ch[x][0], l, mid-1); else Del(ch[x][1], mid+1, r);
}
void del(){int a = rk(rt, len);int b = kth(rt, a - 1), c = kth(rt, a + 1);ans -= hi[len] + hi[c];hi[c] = lcp(b, c);ans += hi[c]; Del(rt, 1, inf); len--;
}
int main(){srand(time(0));scanf("%s", s + 1); n = strlen(s + 1);pw[0] = 1; for(int i = 1; i <= n; i++) pw[i] = pw[i-1] * Base;for(int i = 1; i <= n; i++){if(s[i] == '-') del(); else ins(i);cout << 1ll * len * (len + 1) / 2 - ans << '\n';} return 0;
}

SP8093 JZPGYZ - Sevenk Love Oimaster
建出广义后缀自动机后,把询问串拿上去跑
如果我们对每个模板串的结点标记一个 i d id id 的话,它的答案就是 p a r e n t parent parent 树子树中 i d id id 的个数
因为它子树的所有结点都包涵它
于是处理出 d f s dfs dfs 序后就可以转换为查询区间颜色个数,树状数组即可

#include<cstdio>
#include<string>
#include<vector>
#include<iostream>
#define N 800050
using namespace std;
int n, m, las, node;
int ch[N][26], link[N], len[N];
string S;
vector<int> a[N];
vector<int> v[N];
void extend(int id, int c){if(ch[las][c]){int p = las, q = ch[p][c];if(len[q] == len[p] + 1){ a[q].push_back(id); las = q; return;}int clone = ++node; len[clone] = len[p] + 1; a[clone].push_back(id); link[clone] = link[q];for(int i = 0; i < 26; i++) ch[clone][i] = ch[q][i];link[q] = clone;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;las = clone; return;}int now = ++node, p = las; len[now] = len[p] + 1;a[now].push_back(id);for(;p && !ch[p][c]; p = link[p]) ch[p][c] = now;if(!p) link[now] = 1;else{int q = ch[p][c]; if(len[q] == len[p] + 1) link[now] = q;else{int clone = ++node; len[clone] = len[p] + 1;link[clone] = link[q];for(int i = 0; i < 26; i++) ch[clone][i] = ch[q][i];link[q] = link[now] = clone;for(;p && ch[p][c] == q; p = link[p]) ch[p][c] = clone;}} las = now;
}
int st[N], ed[N], sign, cnt, ans[N];
struct data{ int l, op, id; data(int _l = 0, int _op = 0, int _id = 0){l = _l, op = _op, id = _id;} };
vector<data> q[N];
int vis[N], pre[N], pos[N];
void dfs(int u){st[u] = ++sign; pos[sign] = u;for(int i = 0; i < v[u].size(); i++) dfs(v[u][i]); ed[u] = sign;
}
int c[N];
void Add(int x, int v){ ++x; for(;x<=sign+1; x+=x&-x) c[x] += v;}
int Ask(int x){ ++x; int ans = 0; for(;x;x-=x&-x) ans += c[x]; return ans;}
int main(){scanf("%d%d", &n, &m); node = 1;for(int i = 1; i <= n; i++){cin >> S; int len = S.length(); las = 1;for(int j = 0; j < len; j++) extend(i, S[j] - 'a');}for(int i = 2; i <= node; i++) v[link[i]].push_back(i);dfs(1);for(int i = 1; i <= m; i++){cin >> S; int len = S.length(); int now = 1;for(int j = 0; j < len; j++){now = ch[now][S[j]-'a']; if(now == 0) break;} q[st[now]-1].push_back(data(st[now] - 1, -1, i));q[ed[now]].push_back(data(st[now] - 1, 1, i));}for(int i = 1; i <= sign; i++){for(int j = 0; j < a[pos[i]].size(); j++){int now = a[pos[i]][j]; pre[now] = vis[now]; vis[now] = i;Add(pre[now], 1); }for(int j = 0; j < q[i].size(); j++){data now = q[i][j];ans[now.id] += now.op * Ask(now.l);}}for(int i = 1; i <= m; i++) cout << ans[i] << '\n';return 0;
}

暑假集训 ---- 字符串2 (SAM专题)相关推荐

  1. 暑假集训考试反思+其它乱写

    7.20 Sat 下午返校 回来改题 sdfz的巨佬觉得线上虐人不够爽,所以他们过来了 改T2的时候发现一个问题 如果要用$i$和$i\ xor\ 1$表示相邻的两条边,链式前向星tot初值必须设为1 ...

  2. 2016暑假集训总结

    Preface 这是蒟蒻lyd729在初二升初三的暑假里训练的总结. 来看看一年前的lyd729写的暑假集训总结(链接),真是觉得自己长大了好多. 这一年发生了太多故事.(我来讲故事啦) 去年,一升初 ...

  3. ACM暑假集训总结(2014年夏)

    ACM暑假集训总结 这篇总结本来是打算暑假集训结束后, UVa上刷够300题, 给这一路留个纪念, 可最后竟然给了自己个大耳光--298. Hehe. 既然打算写了,那该干的就得干. 一个耳光没打够, ...

  4. 2016暑假集训小结

    小结 暑假集训之前,有很多事情在忙,什么考试,**之类.集训开始的时候还回了一趟家,这趟回家也发生了对我影响最大的事,之后想了很久,觉得也还是有必要继续走下去,因为我还是很爱这个集训队,很爱思考题解题 ...

  5. 【2021软件创新实验室暑假集训】SpringMVC框架(设计原理、简单使用、源码探究)

    系列文章目录 20级 Java篇 [2021软件创新实验室暑假集训]计算机的起源与大致原理 [2021软件创新实验室暑假集训]Java基础(一) [2021软件创新实验室暑假集训]Java基础(二) ...

  6. 2017暑假集训总结

    这个暑假,和上个寒假一样,还是留在学校进行了集训,进行ACM的训练.相比上个寒假,我的状态算是有了比较大的改变. 总的来说,是我的想法决定了我的行为和学习状态.上个学期只是把ACM当作一个业余爱好来学 ...

  7. 暑假集训后的一些感想

    在开学一个星期后我终于还是写下了这篇有关暑假集训博客,暑假集训的开始时间是七月十八号,是放暑假后的第三个星期.也就是说刚放假时我们还都回家了两个星期,快乐的玩耍了一阵子.随着时间的推移,时间来到七月十 ...

  8. 2015暑假集训总结

    这个暑假,我很明显的感觉,我的许多方面都发现了缺陷或得到了提升. 首先是做比赛.做题策略方面的,我还有很多要改进的地方.比赛时,我总是想到一些有些神奇的想法,有时会接近正解或者就是,有时会相差甚远,打 ...

  9. 2014暑假集训总结

    为期五个星期的暑假集训就这么结束了,如果说寒假的集训是为我们挖了一个个大坑,那么暑假的集训就只能说是宇宙大爆炸了.到了现在才发现,寒假我们学习的东西仅仅只是皮毛而已,这次学习的高级数据结构,真的是把我 ...

最新文章

  1. FPGA 中的latch 锁存器
  2. UPDATE ORACLE(9i OR 10G) USER'S PASSWORD AND GET ORACLE VERSION MESSAGE
  3. 基于SEAL库实现PSI-报错实录1
  4. 200827C阶段一_C++基础
  5. 寄存器讲解--汇编(32位处理器)
  6. apollo源码分析 感知_Kitty中的动态线程池支持Nacos,Apollo多配置中心了
  7. python循环10次_开发一个循环 5 次计算的小游戏, 设置随机种子为10,每次随机产生两个 1~10的数字以及随机选择...
  8. 自定义控件之绘图篇(三):区域(Range)
  9. 计算机类文献检索考试题,文献检索试题精选2
  10. 细究STP根端口和指定端口的选举过程
  11. 在 Linux 命令行发送邮件的 5 种方法
  12. Json文件转Map(三)之获取嵌套Map值
  13. *(volatile unsigned long *) 语法
  14. 渗透测试工具之——初识burp
  15. python 百分号调用内置函数_建议你吃透python这68个内置函数!
  16. 论文写作相关事项汇总
  17. 计算机名人堂(历届图灵奖获得者)
  18. Ubuntu16.04 查看硬盘序列号以及系统版本与安装时间
  19. 虚拟内存,页表,快表,多级页表,倒排页表
  20. 跳台阶,有多少种跳法

热门文章

  1. ARM和neon指令集
  2. 使用R语言进行协整关系检验
  3. Python【二手车价格预测案例】数据挖掘
  4. 迁移mysql数据库报错_数据库迁移失败报错
  5. 程序员,这12个问题让经理比你痛苦多了
  6. 如何在Python中安装NumPy
  7. deny all后如何优雅的处理403
  8. 嵌入式linux数控系统,关于ARM+DSP嵌入式Linux数控系统设计.pdf
  9. 2022年全球与中国PLC光分路器市场现状及未来发展趋势
  10. 记一次失败的《将视频中的音频转换成文字》的经历