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; // failure
int 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;
}