后缀自动机2:hihocoder1445.
题目大意:求串
S
S
S本质不同的子串个数.
1
≤
∣
S
∣
≤
1
0
6
1\leq |S|\leq 10^6
1≤∣S∣≤106.
考虑后缀自动机的原理,确定一个串的方式为确定该串在原串中的右端点和该串的长度,所以只有当两个子串在同一个Right集合中且它们的长度相同才会相同.那么直接构造SAM得到每个节点 i i i表示的长度区间 [ l e n p a r i , l e n i ] [len_{par_i},len_i] [lenpari,leni],点 i i i的贡献即为 l e n i − l e n p a r i len_i-len_{par_i} leni−lenpari.
时间复杂度 O ( ∣ S ∣ Σ ) O(|S|\Sigma) O(∣S∣Σ).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=1000000,C=26;
int n;
char s[N+9];
struct automaton{
int s[C],par,len;
}tr[N*2+9];
int cn,last;
LL ans;
void Build(){cn=last=1;}
void Extend(int x){
int p=last,np=++cn;
tr[np].len=tr[p].len+1;
for (;p&&!tr[p].s[x];p=tr[p].par) tr[p].s[x]=np;
if (!p) tr[np].par=1;
else{
int q=tr[p].s[x];
if (tr[p].len+1==tr[q].len) tr[np].par=q;
else{
tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
tr[np].par=tr[q].par=cn;
for (;p&&tr[p].s[x]==q;p=tr[p].par) tr[p].s[x]=cn;
}
}
last=np;
}
Abigail into(){
scanf("%s",s+1);
n=strlen(s+1);
}
Abigail work(){
Build();
for (int i=1;i<=n;++i) Extend(s[i]-'a');
for (int i=1;i<=cn;++i) ans+=tr[i].len-tr[tr[i].par].len;
}
Abigail outo(){
printf("%lld\n",ans);
}
int main(){
into();
work();
outo();
return 0;
}
后缀自动机3:hihocoder1449.
题目大意:求串
S
S
S长度为
k
k
k的所有子串中出现次数最多的串的出现次数,并求出对于所有
1
1
1到
n
n
n的答案.
1
≤
∣
S
∣
≤
1
0
6
1\leq |S|\leq 10^6
1≤∣S∣≤106.
很容易想到对于一个节点 i i i,设它代表的长度区间为 [ m n i , m x i ] [mn_i,mx_i] [mni,mxi],Right集合大小为 R i R_i Ri,并且设答案序列为 a n s [ k ] ans[k] ans[k].那么这个节点相当于是对 [ m n i , m x i ] [mn_i,mx_i] [mni,mxi]这个区间的所有 a n s ans ans值与 R i R_i Ri取了一个最大值.
发现这是个很经典的问题,可以形式化地写成在一个序列上支持两个操作:
1.求
a
n
s
[
i
]
ans[i]
ans[i]的值.
2.对于
∀
j
∈
[
m
n
i
,
m
x
i
]
\forall j\in[mn_i,mx_i]
∀j∈[mni,mxi],让
a
n
s
[
j
]
=
max
(
a
n
s
[
j
]
,
R
i
)
ans[j]=\max(ans[j],R_i)
ans[j]=max(ans[j],Ri).
发现这个东西可以用线段树直接解决,但是线段树的大常数在这种情况下可能会TLE,而且这个线段树要用永久化标记才能搞.
所以考虑这个问题的性质,注意到对于一个串 S S S, S S S的任意一个子串的出现次数必定不小于 S S S的出现次数,也就是说 a n s [ i ] ans[i] ans[i]是非严格递减的.那么我们考虑对于一个区间修改,可以直接转化为一个前缀的修改,这个时候直接让 a n s [ m x i ] = max ( a n s [ m x i ] , R i ) ans[mx_i]=\max(ans[mx_i],R_i) ans[mxi]=max(ans[mxi],Ri),最后从后往前枚举让 a n s [ i ] = max j = i n a n s [ j ] ans[i]=\max_{j=i}^{n}ans[j] ans[i]=maxj=inans[j],就可以 O ( ∣ S ∣ Σ ) O(|S|\Sigma) O(∣S∣Σ)解决这个问题了.
代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1000000,C=26;
char s[N+9];
int n;
void into(){
scanf("%s",s+1);
n=strlen(s+1);
}
struct automaton{
int s[C],par,len;
}tr[N*2+9];
int cn,last,rgh[N*2+9];
void Build(){cn=last=1;}
void Extend(int c){
int p=last,np=++cn;
tr[last=np].len=tr[p].len+1;rgh[np]=1;
for (;p&&!tr[p].s[c];p=tr[p].par) tr[p].s[c]=np;
if (!p) tr[np].par=1;
else{
int q=tr[p].s[c];
if (tr[p].len+1==tr[q].len) tr[np].par=q;
else{
tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
tr[q].par=tr[np].par=cn;
for (;p&&tr[p].s[c]==q;p=tr[p].par) tr[p].s[c]=cn;
}
}
}
void Get_tr(){
Build();
for (int i=1;i<=n;++i) Extend(s[i]-'a');
}
int cnt[N+9],ord[N*2+9];
void Get_rgh(){
for (int i=1;i<=cn;++i) ++cnt[tr[i].len];
for (int i=1;i<=n;++i) cnt[i]+=cnt[i-1];
for (int i=1;i<=cn;++i) ord[cnt[tr[i].len]--]=i;
for (int i=cn;i>=1;--i){
int t=ord[i];
rgh[tr[t].par]+=rgh[t];
}
}
int ans[N+9];
void Get_ans(){
for (int i=2;i<=cn;++i){
int t=tr[i].len;
ans[t]=max(ans[t],rgh[i]);
}
for (int i=n;i>=1;--i) ans[i]=max(ans[i],ans[i+1]);
}
void work(){
Get_tr();
Get_rgh();
Get_ans();
}
void outo(){
for (int i=1;i<=n;++i)
printf("%d\n",ans[i]);
}
int main(){
into();
work();
outo();
return 0;
}
后缀自动机4:hihocoder1457.
题目大意:给定一些数字串
S
i
S_i
Si,求
S
i
S_i
Si中所有本质不同的子串(在
S
i
,
S
j
(
i
=
̸
j
)
S_i,S_j(i=\not{}j)
Si,Sj(i=j)中都出现过算作一次)的和,对
1
0
9
+
7
10^9+7
109+7取模.
∑
S
i
≤
1
0
6
\sum S_i\leq 10^6
∑Si≤106
先考虑单个串的情况.看到本质不同的子串肯定SAM,然后考虑建出来SAM的性质.
首先考虑SAM的性质,发现SAM必然是一个跑不出重复子串的自动机,那么考虑直接把后缀自动机的转移图建出来,在这上面跑DP,设
f
[
i
]
f[i]
f[i]表示转移到状态
i
i
i得到本质不同的所有子串的和,
c
n
t
[
i
]
cnt[i]
cnt[i]表示转移到状态
i
i
i得到本质不同的所有子串的数量一条边
(
y
,
x
,
v
)
(y,x,v)
(y,x,v)表示从点
y
y
y到点
x
x
x,边权为
v
v
v.那么就可以这么转移:
c
n
t
[
x
]
=
∑
(
y
,
x
,
v
)
∈
E
c
n
t
[
y
]
f
[
x
]
=
∑
(
y
,
x
,
v
)
∈
E
f
[
y
]
∗
10
+
c
n
t
[
y
]
∗
v
cnt[x]=\sum_{(y,x,v)\in E}cnt[y]\\ f[x]=\sum_{(y,x,v)\in E}f[y]*10+cnt[y]*v
cnt[x]=(y,x,v)∈E∑cnt[y]f[x]=(y,x,v)∈E∑f[y]∗10+cnt[y]∗v
有了这个转移后,我们只需要构造出SAM即可.
多个字符串的话直接上广义SAM即可.
时间复杂度均为 O ( ∑ ∣ S i ∣ Σ ) O(\sum |S_i|\Sigma) O(∑∣Si∣Σ).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=1000000,C=10,mod=1000000007;
int add(int a,int b){return a+b>=mod?a+b-mod:a+b;}
int sub(int a,int b){return a-b<0?a-b+mod:a-b;}
int mul(int a,int b){return (LL)a*b%mod;}
void sadd(int &a,int b){a=add(a,b);}
void ssub(int &a,int b){a=sub(a,b);}
void smul(int &a,int b){a=mul(a,b);}
int n,mx;
char s[N+9];
struct automaton{
int s[C],par,len;
}tr[N*2+9];
int cn,last;
void Build(){cn=last=1;}
void Rebuild(){last=1;}
void Extend(int x){
int p=last;
if (tr[p].s[x]){
int q=tr[p].s[x];
if (tr[p].len+1==tr[q].len) last=q;
else{
tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
tr[q].par=cn;
for (;p&&tr[p].s[x]==q;p=tr[p].par) tr[p].s[x]=cn;
last=cn;
}
}else{
int np=++cn;
tr[np].len=tr[p].len+1;
for (;p&&!tr[p].s[x];p=tr[p].par) tr[p].s[x]=np;
if (!p) tr[np].par=1;
else{
int q=tr[p].s[x];
if (tr[p].len+1==tr[q].len) tr[np].par=q;
else{
tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
tr[np].par=tr[q].par=cn;
for (;p&&tr[p].s[x]==q;p=tr[p].par) tr[p].s[x]=cn;
}
}
last=np;
}
}
int cnt[N*2+9],ord[N*2+9],dp[N*2+9];
void Topsort(){
for (int i=1;i<=cn;++i) ++cnt[tr[i].len];
for (int i=1;i<=mx;++i) cnt[i]+=cnt[i-1];
for (int i=1;i<=cn;++i) ord[cnt[tr[i].len]--]=i;
for (int i=1;i<=mx;++i) cnt[i]=0;
cnt[1]=1;
for (int i=1;i<=cn;++i){
int t=ord[i];
for (int j=0;j<C;++j)
if (tr[t].s[j]){
sadd(cnt[tr[t].s[j]],cnt[t]);
sadd(dp[tr[t].s[j]],add(mul(dp[t],10),mul(j,cnt[t])));
}
}
}
int ans;
Abigail into(){
scanf("%d",&n);
Build();
for (int i=1;i<=n;++i){
scanf("%s",s+1);
int m=strlen(s+1);
mx=max(mx,m);
Rebuild();
for (int j=1;j<=m;++j) Extend(s[j]-'0');
}
}
Abigail work(){
Topsort();
for (int i=1;i<=cn;++i) sadd(ans,dp[i]);
}
Abigail outo(){
printf("%d\n",ans);
}
int main(){
into();
work();
outo();
return 0;
}
后缀自动机5:hihocoder1465.
题目大意:给定一个串
S
S
S,和多个串
T
i
T_i
Ti,求对于每个
T
i
T_i
Ti,在
S
S
S中有多少个子串与
T
i
T_i
Ti是循环同构串.
1
≤
∣
S
∣
,
∑
T
i
≤
1
0
5
1\leq|S|,\sum T_i\leq 10^5
1≤∣S∣,∑Ti≤105.
首先若只要求有多少个子串与 T i T_i Ti相同,AC自动机即可,但是这里是要求循环同构串怎么办?
考虑把每一个 T i T_i Ti倍长,那么任意一个下标开始 n n n个字符与 S S S的一个子串相等,这个子串就有贡献.
考虑对 S S S建立SAM,然后直接把每个倍长后的 T i T_i Ti放到自动机里跑,若有转移直接转移,没有就跳parent指针,所有经过的状态都打上标记,最后所有打上标记并且长度区间中包含 n n n的节点的Right集合大小之和即为答案.并且在这个过程中,若到达的某个状态的最短长度大于 n n n了,这个状态也应该跳parent指针.
事实上我们可以把这个过程看作在 T i T_i Ti上设头尾指针,跳转移边相当于尾指针向后,跳parent指针相当于头指针向后,这个过程与在AC自动机上匹配串类似.
再来分析时间复杂度,首先对 S S S建立SAM是 O ( ∣ S ∣ ) O(|S|) O(∣S∣)的,对于每个串 T i T_i Ti,每一步至少会使得头指针或尾指针加 1 1 1,而头尾指针最多都到 ∣ T i ∣ |T_i| ∣Ti∣,也就是说时间复杂度是 O ( ∣ T i ∣ ) O(|T_i|) O(∣Ti∣)的,所以总复杂度就是 O ( ∣ S ∣ + ∑ ∣ T i ∣ ) O(|S|+\sum |T_i|) O(∣S∣+∑∣Ti∣)的.
值得注意的一点,在转移过程中应该记录当前串的长度,若直接调用SAM上的 l e n len len长度则会WA掉.其实这一点可以通过这个状态可能有更长的串可以表示,但是通过前面那条路径无法转移到这个长度来解释.
时间复杂度 O ( ( ∣ S ∣ + ∑ ∣ T i ∣ ) Σ ) O((|S|+\sum |T_i|)\Sigma) O((∣S∣+∑∣Ti∣)Σ).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000,C=26;
struct automaton{
int s[C],len,par;
}tr[N*2+9];
int cn,last,rght[N*2+9];
void Build_sam(){cn=last=1;}
void extend(int x){
int p=last,np=++cn;
tr[np].len=tr[p].len+1;rght[np]=1;
last=np;
while (p&&!tr[p].s[x]) tr[p].s[x]=np,p=tr[p].par;
if (!p) tr[np].par=1;
else {
int q=tr[p].s[x];
if (tr[p].len+1==tr[q].len) tr[np].par=q;
else {
tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
tr[np].par=tr[q].par=cn;
while (tr[p].s[x]==q&&p) tr[p].s[x]=cn,p=tr[p].par;
}
}
}
struct side{
int y,next;
}e[N*2+9];
int lin[N*2+9],top;
void ins(int x,int y){
e[++top].y=y;
e[top].next=lin[x];
lin[x]=top;
}
void Build_parent(){
for (int i=2;i<=cn;++i)
ins(tr[i].par,i);
}
void Get_rght(int k=1){
for (int i=lin[k];i;i=e[i].next){
Get_rght(e[i].y);
rght[k]+=rght[e[i].y];
}
}
int b[N*2+9],tt[N*2+9],ct;
void add(int x){b[x]=1;tt[++ct]=x;}
int Query(char *c,int len){
int x=1,ans=0,now=0;
for (int i=1;i<=len;++i) c[i+len]=c[i];
for (int i=1;i<=len<<1;++i){
while (x&&!tr[x].s[c[i]-'a']) x=tr[x].par,now=tr[x].len;
if (!x) x=1,now=0;
if (tr[x].s[c[i]-'a']) x=tr[x].s[c[i]-'a'],++now; //这里不能写成now=tr[x].len,在上面已经解释过原因了
if (now>len) while (tr[tr[x].par].len>=len) x=tr[x].par,now=tr[x].len;
if (now>=len&&!b[x]) ans+=rght[x],add(x);
}
for (;ct;--ct) b[tt[ct]]=0;
return ans;
}
char s[N*2+9];
int n;
Abigail into(){
scanf("%s",s+1);
n=strlen(s+1);
}
Abigail work(){
Build_sam();
for (int i=1;i<=n;++i)
extend(s[i]-'a');
Build_parent();
Get_rght();
}
Abigail getans(){
int len;
scanf("%d",&n);
while (n--){
scanf("%s",s+1);
len=strlen(s+1);
printf("%d\n",Query(s,len));
}
}
int main(){
into();
work();
getans();
return 0;
}
后缀自动机6:hihocoder1466.
题目大意:给定两个字符串
A
,
B
A,B
A,B,现在要求分别用
A
,
B
A,B
A,B的两个子串
X
,
Y
X,Y
X,Y进行游戏,问字典序第
K
K
K小的两个先手必胜的
X
,
Y
X,Y
X,Y是哪两个.其中
X
,
Y
X,Y
X,Y的字典序比较方式为先比较第一个串的字典序,后比较第二个串的字典序.
游戏规则为,先手开始游戏,两人轮流加字符,先无法操作的人输.其中每一轮操作的规则为,任选
X
,
Y
X,Y
X,Y中的一个,往后面加一个字符使得
X
X
X还是
A
A
A的子串,
Y
Y
Y还是
B
B
B的子串.
1
≤
∣
A
∣
,
∣
B
∣
≤
1
0
5
1\leq|A|,|B|\leq 10^5
1≤∣A∣,∣B∣≤105.
考虑直接对 A , B A,B A,B都建立SAM,由于是要求它们的子串胜负,所以考虑对于SAM上的每个状态求出它的胜负,利用SG函数即可.整个游戏的胜负可以通过SG定理来合并.
值得注意的是,很容易发现SAM上任意一个状态的 s g sg sg值都不会超过字符集大小,因为SAM上每个状态最多连字符集大小条出边.
得到了每一个状态的胜负之后,我们考虑在一个SAM上如何找到第 k k k小的必胜子串,这个问题类似于第 k k k小子串问题,只是我们预处理每一个状态开始的所有子串的时候要忽略必败态,也就是忽略 s g = 0 sg=0 sg=0时候的情况.
两个串的时候,我们可以考虑先确定答案在第一个SAM上的位置,再确定第二个.不过在第一个SAM上跑的时候,要直接减掉可以与正在遍历的状态组成必胜态的子串数量,也就是说 s g = ̸ 0 sg=\not{}0 sg=0,这个时候我们就需要记录 s g sg sg为每一个数时的子串数量了.
时间复杂度 O ( ( ∣ A ∣ + ∣ B ∣ ) Σ 2 ) O((|A|+|B|)\Sigma^2) O((∣A∣+∣B∣)Σ2).
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=100000,C=26;
struct SAM{
struct automaton{
int s[C],len,par;
}tr[N*2+9];
int cn,last,sg[N*2+9],flag[N*2+9][C+1];
LL cnt[N*2+9][C+1],sum[N*2+9];
void extend(int x){
int p=last,np=++cn;
tr[np].len=tr[p].len+1;
last=np;
while (p&&!tr[p].s[x]) tr[p].s[x]=np,p=tr[p].par;
if (!p) tr[np].par=1;
else {
int q=tr[p].s[x];
if (tr[p].len+1==tr[q].len) tr[np].par=q;
else {
tr[++cn]=tr[q];tr[cn].len=tr[p].len+1;
tr[np].par=tr[q].par=cn;
while (tr[p].s[x]==q&&p) tr[p].s[x]=cn,p=tr[p].par;
}
}
}
int get_sg(int k){
if (sg[k]^-1) return sg[k];
for (int i=0;i<=C;++i) flag[k][i]=0;
for (int i=0;i<C;++i)
if (tr[k].s[i]){
flag[k][get_sg(tr[k].s[i])]=1;
for (int j=0;j<=C;++j)
cnt[k][j]+=cnt[tr[k].s[i]][j];
}
int i=0;
while (flag[k][i]) ++i;
sg[k]=i;
++cnt[k][sg[k]];
for (int i=0;i<=C;++i) sum[k]+=cnt[k][i];
return sg[k];
}
void build(char *s,int len){
last=cn=1;
for (int i=1;i<=len;++i)
extend(s[i]-'a');
for (int i=1;i<=cn;++i) sg[i]=-1;
get_sg(1);
}
}A,B;
char a[N+9],b[N+9],sa[N+9],sb[N+9];
int n,m,now;
LL k;
LL calc(int a,int b){
LL sum=0;
for (int i=0;i<=C;++i)
sum+=A.cnt[a][i]*(B.sum[b]-B.cnt[b][i]);
return sum;
}
int dfsa(int x,int pos){
LL sum1=B.sum[1]-B.cnt[1][A.sg[x]],sum2;
if (sum1>=k) return x;
else k-=sum1;
for (int i=0;i<C;++i)
if (A.tr[x].s[i]){
sum2=calc(A.tr[x].s[i],1);
if (sum2<k) k-=sum2;
else {
sa[pos]='a'+i;
return dfsa(A.tr[x].s[i],pos+1);
}
}
return -1;
}
int dfsb(int x,int pos){
k-=A.sg[now]!=B.sg[x];
if (k==0) return x;
LL sum;
for (int i=0;i<C;++i)
if (B.tr[x].s[i]){
sum=B.sum[B.tr[x].s[i]]-B.cnt[B.tr[x].s[i]][A.sg[now]];
if (sum<k) k-=sum;
else {
sb[pos]='a'+i;
return dfsb(B.tr[x].s[i],pos+1);
}
}
return -1;
}
Abigail into(){
scanf("%lld",&k);
scanf("%s",a+1);
n=strlen(a+1);
A.build(a,n);
scanf("%s",b+1);
m=strlen(b+1);
B.build(b,m);
}
Abigail work(){
now=dfsa(1,1);
if (now^-1) dfsb(1,1);
}
Abigail outo(){
if (now==-1) puts("NO");
else printf("%s\n%s\n",sa+1,sb+1);
}
int main(){
into();
work();
outo();
return 0;
}