#include<bits/stdc++.h>
using namespace std;
const int maxn=3e6+150;
const int kind=26;
int tot=1,las=1;
int ch[maxn*2][kind];
int len[maxn*2],fa[maxn*2],cnt[maxn*2];
inline int read() {
char c = getchar();
while (!isalpha(c)) c = getchar();
return c - 'A';
}
inline int newn(int x){len[++tot]=x;return tot;}
//简化版SAM
void sam_ins(int c){
int p=las,np=newn(len[las]+1);las=tot;cnt[np]++;
while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newn(len[p]+1);
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
}
int now=1;
int ans=1;
inline void solve(int v){
if(ch[now][v]){
now=ch[now][v];
}
else{
++ans;
now=ch[1][v];
}
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
for(int i=0;i<n;i++){
sam_ins(read());
}
for(int i=0;i<m;i++){
solve(read());
}
printf("%d\n",ans);
return 0;
}
传送门
TJOI2015弦论
对于一个给定长度为N的字符串,求它的第K小子串是什么。
(位置相同和位置不同的贡献不定)
利用了计数排序的思想,在统计数量时,为了避免漏掉和增多,在统计时候
如果我们计算
s
u
m
[
f
a
[
i
]
]
+
=
s
u
m
[
i
]
sum[fa[i]]+=sum[i]
sum[fa[i]]+=sum[i]时,必须保证i已经统计完毕,根据SAM的性质len[fa[i]]小于len[i],所以可以根据长度排序然后从最长的统计到根节点。
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+150;
const int kind=26;
int n,t,k,tot=1,las=1;
char s[maxn];
int ch[maxn*2][kind];
int len[maxn*2],fa[maxn*2],cnt[maxn*2];
int l,x;
int a[maxn*2],who[maxn*2];
int sum[maxn*2];
inline int newn(int x){len[++tot]=x;return tot;}
//简化版SAM
void sam_ins(int c){
int p=las,np=newn(len[las]+1);las=tot;cnt[np]++;
while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newn(len[p]+1);
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
}
void updatecount(){
for(int i=1;i<=tot;i++)a[len[i]]++;
for(int i=1;i<=tot;i++)a[i]+=a[i-1];
for(int i=1;i<=tot;i++)who[a[len[i]]--]=i;
for(int i=tot;i>=2;i--)cnt[fa[who[i]]]+=cnt[who[i]];
}
char ans[maxn];
int main(){
scanf("%s",s+1);
int slen=strlen(s+1);
scanf("%d%d",&t,&k);
for(int i=1;i<=slen;i++)sam_ins(s[i]-'a');
//计数排序
updatecount();
if(t==0)
for(int i=2;i<=tot;i++)cnt[i]=1;
cnt[1]=0;
for(int i=1;i<=tot;i++)sum[i]=cnt[i];
for(int i=tot;i>=1;i--)
for(int j=0;j<kind;j++)
if(ch[who[i]][j])sum[who[i]]+=sum[ch[who[i]][j]];
int now=1,num=0;
while(k){
for(int i=0;i<kind;i++) if(ch[now][i]){
if(k<=sum[ch[now][i]]){
ans[num++]='a'+i; now=ch[now][i];
k-=cnt[now]; break;
} else k-=sum[ch[now][i]];
}
}
if(k) printf("-1\n");
else{ ans[num]=0; printf("%s\n",ans); }
return 0;
}
SDOI2016生成魔咒
题面
其实就是求SAM新建一个节点的时候,新加了多少个本质不同的子串。
对于新建的一个节点贡献的本质不同的子串数目为
a
n
s
=
l
e
n
[
i
]
−
l
e
n
[
f
a
[
i
]
]
ans=len[i]-len[fa[i]]
ans=len[i]−len[fa[i]]
新添的字符新建的节点就是last,不过这个题目需要map存边。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+150;
typedef long long ll;
int n,tot=1,las=1;
map<int,int>ch[maxn];
int len[maxn],fa[maxn];
inline int newn(int x){len[++tot]=x;return tot;}
ll ans=0;
void sam_ins(int c){
int p=las,np=newn(len[las]+1);las=tot;
while(p&&ch[p].find(c)==ch[p].end())ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newn(len[p]+1);
ch[nq]=ch[q];
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
ans+=len[np]-len[fa[np]];
}
int main(){
scanf("%d",&n);
int c;
for(int i=1;i<=n;i++){
scanf("%d",&c);
sam_ins(c);
printf("%lld\n",ans);
}
return 0;
}
HAOI2016找相同字符
给定两个字符串,求出在两个字符串中各取出一个子串使得这两个子串相同的方案数。两个方案不同当且仅当这两个子串中有一个位置不同。
简单的广义SAM dfs染色可以求本质不同的子串
但是这个题目要求是位置不同的贡献多个,而且只有两个串,所以方法蛮多的。
第一种:
先把第一个串扔进SAM然后统计一个数组
s
u
m
[
i
]
=
s
u
m
[
f
a
[
i
]
]
+
c
n
t
[
i
]
∗
(
l
e
n
[
i
]
−
l
e
n
[
f
a
[
i
]
]
)
sum[i]=sum[fa[i]]+cnt[i]*(len[i]-len[fa[i]])
sum[i]=sum[fa[i]]+cnt[i]∗(len[i]−len[fa[i]])
这个数组表示的是这个点以及其祖先节点,共出现了多少个子串(非本质不同)。
在统计cnt[i](一个节点出现的次数)时,我们需要先将所有的孙子节点统计完在统计祖先节点,即从底向上。
而统计sum恰恰相反,要从上向下。
这样我们在跑第二个串的时候,跑到一个匹配的节点,那肯定它的祖先节点可以匹配,并且我们可以维护匹配的长度l,(l-len[i])*cnt就是当前状态节点贡献的子串的数目。
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+150;
const int kind=26;
typedef long long ll;
int tot=1,las=1;
int ch[maxn*2][kind];
int len[maxn*2],fa[maxn*2],cnt[maxn*2];
char s1[maxn],s2[maxn];
int vis[maxn*2];
int a[maxn*2],who[maxn*2];
ll sum[maxn*2];
inline int newn(int x){len[++tot]=x;return tot;}
void sam_ins(int c){
int p=las,np=newn(len[las]+1);las=tot;cnt[np]++;
while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newn(len[p]+1);
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
}
void updatecout(){
for(int i=1;i<=tot;i++)a[len[i]]++;
for(int i=1;i<=tot;i++)a[i]+=a[i-1];
for(int i=1;i<=tot;i++)who[a[len[i]]--]=i;
for(int i=tot;i>=2;i--)cnt[fa[who[i]]]+=cnt[who[i]];
for(int i=1;i<=tot;i++)sum[who[i]]=sum[fa[who[i]]]+1ll*cnt[who[i]]*(len[who[i]]-len[fa[who[i]]]);
}
int main(){
scanf("%s",s1);
scanf("%s",s2);
int l1=strlen(s1);
for(int i=0;i<l1;i++)
sam_ins(s1[i]-'a');
updatecout();
int now=1;
int l=0;
int l2=strlen(s2);
ll ans=0;
for(int i=0;i<l2;i++){
int x=s2[i]-'a';
if(ch[now][x]){
++l;
now=ch[now][x];
}else{
while(now&&!ch[now][x])now=fa[now];
if(now)l=len[now]+1,now=ch[now][x];
else now=1,l=0;
}
ans+=sum[fa[now]]+1ll*cnt[now]*(l-len[fa[now]]);//(小心爆int)
}
printf("%lld\n",ans);
return 0;
}
第二种:
广义SAM 这里有很多需要注意的地方 首先我们在把las定为1,然后再输入新串,这个时候申请的np节点可能会是重复的 感受一下{ab,abc}这种
void sam_ins(int c){
int p=las,np=newn(len[las]+1);las=tot;cnt[np]++;
while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newn(len[p]+1);
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
}
这时候会有节点表示完全相同的东西 len[fa[i]]==len[i]如果直接进行基数排序会wa SAM能直接基数排序代表拓扑序 成立的条件是 len[i]严格大于len[fa[i]]所以我们需要讨论一下 看看是不是需要新建节点。
先看代码:
inline int newn(int x){len[++tot]=x;return tot;}
void sam_ins(int c){
int p=las;
//主要是这一段
if(ch[p][c]){
int q=ch[p][c];
//判断的条件和申请np其实是一样的。注意更新las的值就行
if (len[q]==len[p]+1){
las=q;
return;
}
int nq=newn(len[p]+1);
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
las=nq;
return;
}
int np=newn(len[las]+1);las=tot;cnt[np]++;
while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
int nq=newn(len[p]+1);
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
while(p&&ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
}
}
我们在判断出是否新建np的时候其实和原版SAM申请nq的时候是一样的。注意更新las.代码这样很臃肿 所以我们可以整理一下。完整的代码如下
#include<bits/stdc++.h>
using namespace std;
const int maxn=5e5+150;
const int kind=26;
typedef long long ll;
int tot=1,las=1;
int ch[maxn*2][kind];
int len[maxn*2],fa[maxn*2],cnt[maxn*2];
char s1[maxn],s2[maxn];
int vis[maxn*2];
int a[maxn*2],who[maxn*2];
ll sum[maxn*2];
int d1[maxn*2],d2[maxn*2];
inline int newn(int x){len[++tot]=x;return tot;}
inline int newnq(int p,int w){
int nq=newn(len[p]+1);
int q=ch[p][w];
for(int i=0;i<kind;i++)ch[nq][i]=ch[q][i];
fa[nq]=fa[q];
fa[q]=nq;
while(p&&ch[p][w]==q)ch[p][w]=nq,p=fa[p];
return nq;
}
void sam_ins(int c){
int p=las;
if(ch[p][c]){
int q=ch[p][c];
if (len[q]==len[p]+1)las=q;
else las=newnq(p,c);
return ;
}
int np=newn(len[las]+1);las=tot;cnt[np]++;
while(p&&!ch[p][c])ch[p][c]=np,p=fa[p];
if(!p)fa[np]=1;
else{
int q=ch[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else{
fa[np]=newnq(p,c);
}
}
}
void updatecout(){
for(int i=1;i<=tot;i++)a[len[i]]++;
for(int i=1;i<=tot;i++)a[i]+=a[i-1];
for(int i=1;i<=tot;i++)who[a[len[i]]--]=i;
for(int i=tot;i;i--)d1[fa[who[i]]]+=d1[who[i]],d2[fa[who[i]]]+=d2[who[i]];
}
int main(){
scanf("%s",s1);
scanf("%s",s2);
int l1=strlen(s1);
for(int i=0;i<l1;i++)
sam_ins(s1[i]-'a'),d1[las]++;
las=1;
int l2=strlen(s2);
for(int i=0;i<l2;i++)
sam_ins(s2[i]-'a'),d2[las]++;
updatecout();
ll ans=0;
for(int i=1;i<=tot;i++)
ans+=1ll*(len[i]-len[fa[i]])*d1[i]*d2[i];
printf("%lld\n",ans);
return 0;
}