SAM 做题记录
1.基本
SAM 能表示某个字符串的所有子串,且正好是所有子串。
int len[maxn],lnk[maxn];
int a[maxn][26];
int p=1,cur=1;
int ed[maxn];
void insert(int c){
int nd=++cur;
len[nd]=len[p]+1;
while(p&&!a[p][c])a[p][c]=nd,p=lnk[p];
if(!p){lnk[p=nd]=1;return ;}
int q=a[p][c];
if(len[p]+1==len[q])lnk[nd]=q;
else{
int cl=++cur;
len[cl]=len[p]+1;lnk[cl]=lnk[q];
memcpy(a[cl],a[q],sizeof(a[q]));
lnk[nd]=lnk[q]=cl;
while(p&&a[p][c]==q)a[p][c]=cl,p=lnk[p];
}
p=nd;
}
2.应用
P3804
求子串出现次数乘上子串长度的最大值。
每个节点对应多个子串,最长的长为
先把每个前缀所属的点的 siz 设为
int siz[maxn];
void insert(int c){
int nd=++cur;
a[nd].len=a[p].len+1;
siz[nd]=1;
...
}
void dfs(int u){
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
dfs(v);
siz[u]+=siz[v];
}
}
int calc(){
int res=0;
for(int i=2;i<=cur;i++)add(a[i].lnk,i);
dfs(1);
for(int i=2;i<=cur;i++)if(siz[i]>1)res=max(res,siz[i]*a[i].len);
return res;
}
P2408
不同子串个数。
1.dp
在后缀自动机上从根节点开始的每一条路径都是一个子串,求出路径数量,便可以求出子串数。建 SAM dp。
void dfs(int u){
if(dp[u])return ;
dp[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;dfs(v);
dp[u]+=dp[v];
}
}
2.定义
SAM 一个节点对应的长度分别算贡献。parent tree 上,
for(int i=1;i<=n;i++)insert(c[i]-'a'),ans+=len[p]-len[lnk[p]];
对于每个
P3975
字典序第
SP1811
两个字符串的最长公共子串长度。
对
-
如果
有出边 , , 加 。 -
否则向上跳
, 减 ,当 结束。
void go(int &p,int c,int &l){
while(1){
if(a[p][c]){
p=a[p][c],l++;
break;
}
if(!l)break;
l--;
if(l==len[lnk[p]])p=lnk[p];
}
}
P5546
以
CF235C
求每个询问串的所有循环同构在主串中出现的次数总和。
向后遍历同上题,现在需要删掉开头。
删除开头
void del(int &pos,int &l,int n){
if(l>n&&--l==len[lnk[pos]])pos=lnk[pos];
}
P6640
询问
对
拆开 min。找出
P4094
子串
二分答案
记录
注意,是在 DAG 上线段树合并,不能破坏原有结构,新建点。
int n,m;
char c[maxn];
namespace sgt{
#define mid (l+r>>1)
#define ls lc[nd]
#define rs rc[nd]
bool tree[maxn<<5];
int lc[maxn<<5],rc[maxn<<5];
int rt[maxn],idx;
void updata(int &nd,int l,int r,int p,int w){
if(!nd)nd=++idx;
if(l==r){tree[nd]=w;return ;}
if(p<=mid)updata(ls,l,mid,p,w);
else updata(rs,mid+1,r,p,w);
tree[nd]=tree[ls]|tree[rs];
}
int merge(int u,int v,int l,int r){
if(!u||!v)return u|v;
int nd=++idx;
if(l==r){tree[nd]=tree[u]|tree[v];return nd;}
ls=merge(lc[u],lc[v],l,mid);
rs=merge(rc[u],rc[v],mid+1,r);
tree[nd]=tree[ls]|tree[rs];
return nd;
}
bool query(int nd,int l,int r,int ql,int qr){
if(l>=ql&&r<=qr)return tree[nd];
if(qr<=mid)return query(ls,l,mid,ql,qr);
if(ql>mid)return query(rs,mid+1,r,ql,qr);
return query(ls,l,mid,ql,qr)|query(rs,mid+1,r,ql,qr);
}
#undef mid
}
struct sam{
int head[maxn],tot;
struct edgend{
int nxt,to;
}e[maxn<<1];
void add(int u,int v){e[++tot]={head[u],v};head[u]=tot;}
int f[maxn][19];
void dfs(int u){
for(int i=1;i<19;i++)f[u][i]=f[f[u][i-1]][i-1];
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
f[v][0]=u;dfs(v);
sgt::rt[u]=sgt::merge(sgt::rt[u],sgt::rt[v],1,n);
}
}
void build(char *c,int n){
for(int i=1;i<=n;i++)insert(c[i]-'a');
for(int i=1,nd=1;i<=n;i++){
nd=a[nd][c[i]-'a'];
ed[i]=nd;
}
for(int i=2;i<=cur;i++)add(lnk[i],i);
dfs(1);
}
int find(int l,int r){
int res=ed[r];
for(int i=18;~i;i--)if(f[res][i]&&len[f[res][i]]>=r-l+1)res=f[res][i];
return res;
}
}s;
bool check(int a,int b,int l,int r){
int pos=s.find(l,r);
return sgt::query(sgt::rt[pos],1,n,a+r-l,b);
}
P5576
P5546 做区间询问。
选
猫树分治
取阈值
开一个大数组,然后用指针标记位置。
int _sl[maxn],*sl[maxn],*sum;
void sovle(int l,int r,int ql,int qr,int lim){
if(ql>qr)return ;
int tot=0;
while(1){
for(int i=l;i<=r;i++)if(siz[i]<=lim)tmp[++tot]=i;
if(tot)break;lim<<=1;
}
int mid=tmp[1+tot>>1],qmid=ql-1;
sum=_sl;
for(int i=l;i<=r;i++){
sl[i]=sum;
sum+=siz[mid]+1;
}
for(int j=0;j<siz[mid];j++)sl[mid][j]=j+1;
for(int i=mid+1;i<=r;i++){
int p=rt[i],d=0;
for(int j=0;j<siz[mid];j++){
int c=s[mid][j]-'0';
go(p,c,d);
sl[i][j]=min(sl[i-1][j],d);
}
}
for(int i=mid-1;i>=l;i--){
int p=rt[i],d=0;
for(int j=0;j<siz[mid];j++){
int c=s[mid][j]-'0';
go(p,c,d);
sl[i][j]=min(sl[i+1][j],d);
}
}
int tr=0;
for(int i=ql;i<=qr;i++){
int u=id[i];
if(qq[u].r<mid)id[++qmid]=u;
else if(qq[u].l>mid)tmp[++tr]=u;
else{
for(int j=0;j<siz[mid];j++)ans[u]=max(ans[u],min(sl[qq[u].l][j],sl[qq[u].r][j]));
}
}
for(int i=1;i<=tr;i++)id[qmid+i]=tmp[i];
qr=qmid+tr;
sovle(l,mid-1,ql,qmid,lim);sovle(mid+1,r,qmid+1,qr,lim);
}
CF666E
求
对
P4248
P2178
翻转建 SAM,lcp 转换为最长公共后缀,即 parent tree 上的 lca 的 len。
P7361
求
用 set 启发式合并 edp 集合,一个长度的贡献为相邻的 edp 点。贡献为
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步