後綴數組板子
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
int n;
const int N = 1000010;
int sa[N], x[N], c[N], y[N];
char s[N];
inline void SA()
{
for (int i = 1; i <= n; ++i) x[i] = s[i];
int m = 128;
for (int i = 0; i <= m; ++i) c[i] = 0;
for (int i = 1; i <= n; ++i) c[x[i]]++;
for (int i = 1; i <= m; ++i) c[i] += c[i-1];
for (int i = n; i; i--) sa[c[x[i]]--] = i;
for (int k = 1, p; k <= n; k <<= 1){
p = 0;
for (int i = n; i > n - k; i--) y[++p] = i;
for (int i = 1; i <= n; ++i)
if(sa[i] > k) y[++p] = sa[i] - k;
for (int i = 0; i <= m; ++i) c[i] = 0;
for (int i = 1; i <= n; ++i) c[x[i]]++;
for (int i = 1; i <= m; ++i) c[i] += c[i-1];
for (int i = n; i; i--) sa[c[x[y[i]]]--] = y[i];
p = y[sa[1]] = 1;
for (int i = 2, a, b; i <= n; ++i){
a = sa[i] + k > n? -1 : x[sa[i] + k];
b = sa[i-1] + k > n?-1 : x[sa[i-1] + k];
y[sa[i]] = (x[sa[i]] == x[sa[i-1]] && (a == b)? p : ++p);
}
swap(x, y);
m = p;
if (p == n)break;
}
}
int height[N], rk[N], st[N][20];
inline void build_H(){
int k = 0;
for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
for (int i = 1; i <= n; ++i){
if (rk[i] == 1) continue;
if (k) --k;
int j = sa[rk[i]-1];
while(j + k <= n && i + k <= n && s[i+k]==s[j+k]) ++k;
height[rk[i]] = k;
}
}
inline void build_lcp(){
for (int i = 1; i <= n; ++i)st[i][0] = height[i];
for (int i = 1; i <= 17; ++i){
for (int j = 1; j+(1<<i)-1 <= n; ++j)
st[j][i] = max(st[j][i-1], st[j+(1<<(i-1))][i-1]);
}
}
inline int query(int l, int r){
int k = log2(r - l + 1);
return max(st[l][k], st[r-(1<<k)+1][k]);
}
int main()
{
scanf("%s", s + 1);
n = strlen(s + 1);
SA();
build_H();
for (int i = 1; i <= n; ++i)printf("%d ", sa[i]);
}
後綴自動機板子
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
const int N = 40010;
struct state{
int len, link;
int next[180];
}st[N];
int sz, last;
int s[N], a[N], n;
int l[N], r[N], c[N], q[N];
void sam_init(){
memset(st, 0, sizeof(st));
sz = last = 1;
}
void sam_extend(int c){
int cur = ++sz;
st[cur].len = st[last].len + 1;
int p = last;
while(p && !st[p].next[c]){
st[p].next[c] = cur;
p = st[p].link;
}
if (!p){
st[cur].link = 1;
} else {
int q = st[p].next[c];
if (st[p].len + 1 == st[q].len) {
st[cur].link = q;
} else {
int clone = ++sz;
st[clone].len = st[p].len + 1;
memcpy(st[clone].next, st[q].next, sizeof(st[q].next));
st[clone].link = st[q].link;
while(p && st[p].next[c] == q) {
st[p].next[c] = clone;
p = st[p].link;
}
st[q].link = st[cur].link = clone;
}
}
last = cur;
}
poj 1743
求最長不重疊子串,建好後綴樹之後做樹形dp,保存子串出現的左端點和右端點,答案是子串長和右端點和左端點的較小值。
樹形dp不用跑dfs,因爲是按照後綴鏈接來跑dp,結點中只保存了父親指針不能直接dfs,但是因爲長度長的一定在後綴樹的下端,所以根據串長排個序,然後從後往前掃,這樣能保證一定用下端的更新上端結點。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
const int N = 40010;
struct state{
int len, link;
int next[180];
}st[N];
int sz, last;
int s[N], a[N], n;
int l[N], r[N], c[N], q[N];
void sam_init(){
memset(l, 63, sizeof(l));
memset(r, 0, sizeof(r));
memset(c, 0, sizeof(c));
memset(q, 0, sizeof(q));
memset(st, 0, sizeof(st));
sz = last = 1;
}
void sam_extend(int c){
int cur = ++sz;
st[cur].len = st[last].len + 1;
l[cur] = r[cur] = st[cur].len;
int p = last;
while(p && !st[p].next[c]){
st[p].next[c] = cur;
p = st[p].link;
}
if (!p){
st[cur].link = 1;
} else {
int q = st[p].next[c];
if (st[p].len + 1 == st[q].len) {
st[cur].link = q;
} else {
int clone = ++sz;
st[clone].len = st[p].len + 1;
memcpy(st[clone].next, st[q].next, sizeof(st[q].next));
st[clone].link = st[q].link;
while(p && st[p].next[c] == q) {
st[p].next[c] = clone;
p = st[p].link;
}
st[q].link = st[cur].link = clone;
}
}
last = cur;
}
int main()
{
while(scanf("%d", &n)){
if (!n)break;
int ans = 0;
for (int i = 1; i <= n; ++i){
scanf("%d", &s[i]);
a[i] = s[i] - s[i - 1];
}
sam_init();
for (int i = 2; i <= n; ++i){
sam_extend(a[i] + 88);
}
for (int i = 1; i <= sz; ++i)c[st[i].len]++;
for (int i = 1; i < n; ++i) c[i] += c[i-1];
for (int i = sz; i; i--)q[c[st[i].len]--] = i;
for (int i = sz; i; i--){
int p = q[i];
l[st[p].link] = min(l[st[p].link], l[p]);
r[st[p].link] = max(r[st[p].link], r[p]);
}
for (int i = 1; i <= sz; ++i)
ans = max(ans, min(st[i].len, r[i] - l[i]));
if (ans < 4)puts("0");
else printf("%d\n", ans + 1);
}
}
bzoj3238
兩種做法,用SA好想一些,維護一個單調棧
SAM做法:倒着建立後綴樹,這樣兩個結點的後綴的lcp就變成了兩個結點的最長公共後綴,在後綴樹上就是兩個結點的LCA長度,在後綴樹上做樹形DP就可以。
個人感覺SA好想還好寫
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
using namespace std;
const int N = 500010;
int n;
int sa[N], x[N], y[N], c[N];
char s[N];
inline void SA(){
for (int i = 1; i <= n; ++i) x[i] = s[i];
int m = 128;
for (int i = 0; i <= m; ++i) c[i] = 0;
for (int i = 1; i <= n; ++i) c[x[i]]++;
for (int i = 1; i <= m; ++i) c[i] += c[i-1];
for (int i = n; i; i--) sa[c[x[i]]--] = i;
for (int k = 1, p; k <= n; k <<= 1){
p = 0;
for (int i = n; i > n - k; i--) y[++p] = i;
for (int i = 1; i <= n; ++i)
if (sa[i] > k) y[++p] = sa[i] - k;
for (int i = 0; i <= m; ++i) c[i] = 0;
for (int i = 1; i <= n; ++i) c[x[i]]++;
for (int i = 1; i <= m; ++i) c[i] += c[i-1];
for (int i = n; i; i--) sa[c[x[y[i]]]--] = y[i];
p = y[sa[1]] = 1;
for (int i = 2, a, b; i <= n; ++i){
a = sa[i] + k > n? -1 : x[sa[i] + k];
b = sa[i-1] + k > n? -1 : x[sa[i-1] + k];
y[sa[i]] = x[sa[i]] == x[sa[i-1]] && (a == b)? p : ++p;
}
swap(x, y);
m = p;
if (p == n) break;
}
}
int height[N], rk[N];
inline void build_H(){
int k = 0;
for (int i = 1; i <= n; ++i) rk[sa[i]] = i;
for (int i = 1; i <= n; ++i){
if (rk[i] == 1) continue;
if (k) --k;
int j = sa[rk[i]-1];
while(j + k <= n && i + k <= n && s[i+k] == s[j+k]) ++k;
height[rk[i]] = k;
}
}
struct node{
int len, pos;
long long val;
}st[N];
inline long long solve(){
int tp = 0;
long long ans1 = 0, ans = 0;
st[0].pos = 0;
for (int i = 2; i <= n; ++i){
while(tp && st[tp].len > height[i]) tp--;
if (st[tp].len == height[i]) st[tp].val += 1ll * (i - 1 - st[tp].pos) * height[i], st[tp].pos = i - 1;
else st[tp + 1] = (node){height[i], i - 1, st[tp].val + 1ll * (i - 1 - st[tp].pos) * height[i]}, tp++;
ans1 += st[tp].val;
ans += 1ll * (i - 1) * (n - 1);
}
ans += 1ll * n * (n - 1);
// cout<<ans<<endl;
return ans - ans1 * 2;
}
int main()
{
scanf("%s", s + 1);
n = strlen(s + 1);
SA();
build_H();
// for (int i = 1; i <= n; ++i)cout<<height[i] <<' ';
// cout<<endl;
printf("%lld\n", solve());
}
bzoj4516
題意是求對每一個前綴求不同子串個數
思考SA求不同子串個數的求法,是對於每個後綴來找,所以倒着建串,就可以對每個前綴求不同子串的個數。按照題目要求將操作離線,然後倒着掃一遍+鏈表維護一下就好。
這個題用SAM比較簡單,每個前綴的子串個數就是用當前結點減去父親結點的最長長度就可以了,思維難度也不高
#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <map>
using namespace std;
const int N = 100010;
struct state{
int len, link;
map<int, int>nxt;
}st[N*2];
int sz, last;
int s[N], n;
inline void sam_init(){
sz = last = 1;
}
inline int sam_extend(int c){
int cur = ++sz;
st[cur].len = st[last].len + 1;
int p = last;
while(p && !st[p].nxt.count(c)){
st[p].nxt[c] = cur;
p = st[p].link;
}
if (!p){
st[cur].link = 1;
} else {
int q = st[p].nxt[c];
if (st[p].len + 1 == st[q].len){
st[cur].link = q;
} else {
int clone = ++sz;
st[clone].len = st[p].len + 1;
st[clone].nxt = st[q].nxt;
st[clone].link = st[q].link;
while(p && st[p].nxt[c] == q){
st[p].nxt[c] = clone;
p = st[p].link;
}
st[q].link = st[cur].link = clone;
}
}
last = cur;
// printf("%d %d %d %d\n", cur, st[cur].link, st[cur].len, st[st[cur].link].len);
return st[cur].len - st[st[cur].link].len;
}
int main()
{
long long ans = 0;
cin>>n;
sam_init();
for (int i = 1; i <= n; ++i){
scanf("%d", &s[i]);
ans += 1ll * sam_extend(s[i]);
printf("%lld\n", ans);
}
}