【前言】
SAM
\text{SAM}
SAM真是一个玄学数据结构。
【题目】
CF700E Cool Slogans
CF
给定一个字符串
s
[
1
]
s[1]
s[1]。一个字符串序列
s
[
]
s[ ]
s[]满足
s
[
i
]
s[i]
s[i]至少在
s
[
i
−
1
]
s[i-1]
s[i−1]中出现过两次。求最大可行序列长。
n
≤
2
×
1
0
5
n\leq 2\times 10^5
n≤2×105
首先建出
SAM
\text{SAM}
SAM
根据后缀自动机的性质,满足条件的序列一定是
p
a
r
e
n
t
parent
parent树上一条链去掉一些节点。
一个串
A
A
A在另一个串
B
B
B中出现两次,则
r
i
g
h
t
(
A
)
right(A)
right(A)在
B
[
r
i
g
h
t
(
B
)
−
m
x
(
A
)
,
r
i
g
h
t
(
B
)
]
B[right(B)-mx(A),right(B)]
B[right(B)−mx(A),right(B)]中至少出现过两次,更具体地说,我们任取
r
i
g
h
t
(
B
)
right(B)
right(B)中的一个
e
n
d
p
o
s
(
B
)
endpos(B)
endpos(B),则满足
r
i
g
h
t
(
A
)
right(A)
right(A)在
s
[
e
n
d
p
o
s
(
B
)
−
(
m
x
(
B
)
−
m
x
(
A
)
)
,
e
n
d
p
o
s
(
B
)
]
s[endpos(B)-(mx(B)-mx(A)),endpos(B)]
s[endpos(B)−(mx(B)−mx(A)),endpos(B)]中至少出现两次。由于
A
,
B
A,B
A,B是父子关系,那么
r
i
g
h
t
(
A
)
right(A)
right(A)一定在
e
n
d
p
o
s
(
B
)
endpos(B)
endpos(B)出现过一次,因此我们只需要判断
A
A
A是否在
s
[
e
n
d
p
o
s
(
B
)
−
(
m
x
(
B
)
−
m
x
(
A
)
)
,
e
n
d
p
o
s
(
B
)
]
s[endpos(B)-(mx(B)-mx(A)),endpos(B)]
s[endpos(B)−(mx(B)−mx(A)),endpos(B)]中出现过即可。
我们用线段树合并
r
i
g
h
t
right
right集合查询即可。
复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10,M=N*50;
int n,ans;
int f[N],b[N],c[N],rt[N],pos[N];
char s[N];
struct Segment
{
int sz,ls[M],rs[M];
void update(int &x,int l,int r,int p)
{
if(!x) x=++sz;
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) update(ls[x],l,mid,p);
else update(rs[x],mid+1,r,p);
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
int z=++sz;
ls[z]=merge(ls[x],ls[y]);
rs[z]=merge(rs[x],rs[y]);
return z;
}
bool query(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(L<=l && r<=R) return 1;
int mid=(l+r)>>1;bool res=0;
if(L<=mid) res|=query(ls[x],l,mid,L,R);
if(R>mid) res|=query(rs[x],mid+1,r,L,R);
return res;
}
}tr;
struct SAM
{
int sz,las,fa[N],mx[N],endpos[N],ch[N][26];
void init(){sz=las=1;}
void extend(int x,int c)
{
int p,q,np,nq;
p=las;las=np=++sz;mx[np]=mx[p]+1;endpos[np]=c;
for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
if(!p) fa[np]=1;
else
{
q=ch[p][x];
if(mx[q]==mx[p]+1) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;endpos[nq]=c;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
}
}
}
void merge()
{
for(int i=1;i<=sz;++i) b[mx[i]]++;
for(int i=1;i<=sz;++i) b[i]+=b[i-1];
for(int i=sz;i;--i) c[b[mx[i]]--]=i;
for(int i=sz,x;i>1;--i) x=c[i],rt[fa[x]]=tr.merge(rt[fa[x]],rt[x]);
}
void solve()
{
for(int i=2;i<=sz;++i)
{
int x=c[i];
if(fa[x]==1) f[x]=1,pos[x]=x;
else if(tr.query(rt[pos[fa[x]]],1,n,endpos[x]-mx[x]+mx[pos[fa[x]]],endpos[x]-1)) f[x]=f[fa[x]]+1,pos[x]=x;
else f[x]=f[fa[x]],pos[x]=pos[fa[x]];
ans=max(ans,f[x]);
}
printf("%d\n",ans);
}
}S;
int main()
{
#ifndef ONLINE_JUDGE
freopen("CF700E.in","r",stdin);
freopen("CF700E.out","w",stdout);
#endif
scanf("%d%s",&n,s+1);S.init();
for(int i=1;i<=n;++i) S.extend(s[i]-'a',i),tr.update(rt[S.las],1,n,i);
S.merge();S.solve();
return 0;
}
BZOJ3413 匹配
BZOJ
给定一个字符串
S
S
S,有多组询问字符串
T
T
T,问
T
T
T在
S
S
S中暴力匹配,匹配多少次
T
T
T会第一次出现。
∣
S
∣
≤
1
0
5
,
∑
∣
T
∣
≤
3
×
1
0
6
|S|\leq 10^5,\sum |T| \leq 3\times 10^6
∣S∣≤105,∑∣T∣≤3×106
一开始以为是后缀数组lcp思博题。
我们考虑转化问题,相当于求
T
T
T的每个前缀会贡献多少次。
首先建出
SAM
\text{SAM}
SAM,我们可以得到
T
T
T的失配次数以及整个串匹配成功的位置。
设
m
i
x
mi_x
mix表示
p
a
r
e
n
t
parent
parent树上
x
x
x子树内代表
S
S
S一个后缀节点的最小编号匹配一次即可。
现在再考虑
T
T
T每个前缀
i
i
i的贡献,我们要求的就是当前节点
x
x
x子树内,位置小于某个上限的
e
n
d
p
o
s
endpos
endpos数量即可。显然这个上限就是
p
o
s
−
m
x
+
i
pos-mx+i
pos−mx+i,因为再大就会超出第一次匹配的位置。
求
e
n
d
p
o
s
endpos
endpos的数量可以直接线段树合并(
e
n
d
p
o
s
endpos
endpos就是
r
i
g
h
t
right
right集合233)。
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10,M=N*40;
namespace Tree
{
int rt[N];
struct Segment
{
int sz,ls[M],rs[M],val[M];
void update(int &x,int l,int r,int p)
{
if(!x) x=++sz;++val[x];
if(l==r) return;
int mid=(l+r)>>1;
if(p<=mid) update(ls[x],l,mid,p);
else update(rs[x],mid+1,r,p);
}
int query(int x,int l,int r,int L,int R)
{
if(!x || L>R) return 0;
if(L<=l && r<=R) return val[x];
int mid=(l+r)>>1,res=0;
if(L<=mid) res+=query(ls[x],l,mid,L,R);
if(R>mid) res+=query(rs[x],mid+1,r,L,R);
return res;
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
int z=++sz;
ls[z]=merge(ls[x],ls[y]);
rs[z]=merge(rs[x],rs[y]);
val[z]=val[ls[z]]+val[rs[z]];
return z;
}
}tr;
}
using namespace Tree;
namespace SAM
{
struct SAM
{
int sz,las;
int b[N],c[N],mx[N],mi[N],fa[N],ch[N][10];
void init(){sz=las=1;memset(mi,0x3f,sizeof(mi));}
void extend(int x)
{
int p,q,np,nq;
p=las;las=np=++sz;mx[np]=mx[p]+1;
for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
if(!p) fa[np]=1;
else
{
q=ch[p][x];
if(mx[q]==mx[p]+1) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
}
}
}
void merge()
{
for(int i=1;i<=sz;++i) b[mx[i]]++;
for(int i=1;i<=sz;++i) b[i]+=b[i-1];
for(int i=sz;i;--i) c[b[mx[i]]--]=i;
for(int i=sz,x;i>1;--i) x=c[i],rt[fa[x]]=tr.merge(rt[fa[x]],rt[x]),mi[fa[x]]=min(mi[fa[x]],mi[x]);
}
}S;
}
using namespace SAM;
namespace DreamLolita
{
int n,m,T;
char s[N];
int check(char *a)
{
int now=1,n=strlen(a+1);
for(int i=1;i<=n;++i)
{
int x=a[i]^48;
if(S.ch[now][x]) now=S.ch[now][x]; else return -1;
}
//cerr<<now<<endl;
return S.mi[now];
}
void solve()
{
scanf("%d%s",&n,s+1);S.init();m=n;
for(int i=1;i<=n;++i) S.extend(s[i]^48),tr.update(rt[S.las],1,m,i),S.mi[S.las]=i;
S.merge();scanf("%d",&T);
while(T--)
{
scanf("%s",s+1);int h=check(s);n=strlen(s+1);
int ans=(~h?h-n:m),now=1;
//printf("firstmatch:%d contribute:%d\n",h,ans);
for(int i=1;i<=n;++i)
{
int x=s[i]^48;
if(S.ch[now][x]) now=S.ch[now][x]; else break;
if(~h) ans+=tr.query(rt[now],1,m,1,h-n+i);
else ans+=tr.query(rt[now],1,m,1,m);
//printf("%d ",ans);
}
printf("%d\n",ans);
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ3413.in","r",stdin);
freopen("BZOJ3413.out","w",stdout);
#endif
DreamLolita::solve();
return 0;
}
BZOJ2806 [CTSC2012] Cheat
BZOJ
如果我们处理出每个位置为终点往前最多能匹配多长,我们可以用一个二分答案后的
DP
\text{DP}
DP解决这个问题。
具体来说,令
f
i
f_i
fi表示前
i
i
i个字符最大匹配长度,则
f
i
=
max
{
f
j
+
i
−
j
}
(
i
−
j
>
m
i
d
)
f_i=\max \{f_j+i-j\}(i-j>mid)
fi=max{fj+i−j}(i−j>mid)
这个东西显然可以用单调队列来优化。
于是我们上
SAM
\text{SAM}
SAM来求匹配长度即可。(当然后缀数组也是可以的,然而我们在练
SAM
\text{SAM}
SAM)
复杂度
O
(
n
log
n
)
O(n\log n)
O(nlogn)
#include<bits/stdc++.h>
using namespace std;
const int N=1111111;
namespace SAM
{
struct SAM
{
int sz,las,fa[N],mx[N],ch[N][2];
void init(){sz=las=1;}
void extend(int x)
{
int p,q,np,nq;
if(ch[las][x])
{
p=las;q=ch[p][x];
if(mx[q]==mx[p]+1) {las=q;return;}
nq=++sz;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
las=nq;return;
}
p=las;las=np=++sz;mx[np]=mx[p]+1;
for(;p && !ch[p][x];p=fa[p]) ch[p][x]=np;
if(!p) fa[np]=1;
else
{
q=ch[p][x];
if(mx[q]==mx[p]+1) fa[np]=q;
else
{
nq=++sz;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[nq]=fa[q];fa[q]=fa[np]=nq;
for(;ch[p][x]==q;p=fa[p]) ch[p][x]=nq;
}
}
}
}S;
}
using namespace SAM;
namespace DreamLolita
{
int ck,st,p[N],q[N],f[N];
char s[N];
void insert(char *a)
{
int n=strlen(a+1);S.las=1;
for(int i=1;i<=n;++i) S.extend(a[i]^48);
}
void init(char *a)
{
int n=strlen(a+1),now=1,mt=0;
for(int i=1;i<=n;++i)
{
int x=a[i]^48;
if(S.ch[now][x]) now=S.ch[now][x],++mt;
else
{
while(now && !S.ch[now][x]) now=S.fa[now];
if(!now) now=1,mt=0;
else mt=S.mx[now]+1,now=S.ch[now][x];
}
p[i]=mt;
}
}
bool dp(int n,int k)
{
int h=1,t=0;
for(int i=1;i<=n;++i)
{
f[i]=f[i-1];
if(i<k) continue;
while(h<=t && f[q[t]]-q[t]<=f[i-k]-i+k) --t;
q[++t]=i-k;
while(h<=t && q[h]<i-p[i]) ++h;
if(h<=t) f[i]=max(f[i],f[q[h]]+i-q[h]);
}
return f[n]*10>=n*9;
}
void solve()
{
scanf("%d%d",&ck,&st);S.init();
for(int i=1;i<=st;++i) scanf("%s",s+1),insert(s);
while(ck--)
{
scanf("%s",s+1);init(s);int n=strlen(s+1);
//for(int i=1;i<=n;++i) printf("%d ",p[i]); puts("");
int l=1,r=n,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(dp(n,mid)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
}
}
int main()
{
#ifndef ONLINE_JUDGE
freopen("BZOJ2806.in","r",stdin);
freopen("BZOJ2806.out","w",stdout);
#endif
DreamLolita::solve();
return 0;
}