首页 > 其他分享 >SA&SAM 不怎么详细的详解

SA&SAM 不怎么详细的详解

时间:2023-07-07 17:14:34浏览次数:36  
标签:SAM minlen int slink maxlen 详解 字符串 SA sa

后缀数组(SA):将一个字符串的所有后缀排序得到的数组。
算法:倍增+双关键字基数排序。
算法流程:

  • 首先对所有字符排序,记下每个位置的排名。
  • 将相邻两个字符看作一个整体,用他们的两个排名分别作为两个关键字排序。
  • 将相邻两个“两个字符”看作一个整体,用他们的两个排名分别作为两个关键字排序。
  • ……

Code:(洛谷模板P3809)

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5;
int n,m,x[N],y[N],c[N],sa[N];
char s[N];
int main(){
	scanf("%s",s+1);
	n=strlen(s+1);m=300;//m表示值域,开到ASCLL码范围之外肯定可以
	for(int i=1;i<=n;i++){x[i]=s[i];++c[x[i]];}
	for(int i=2;i<=m;i++)c[i]+=c[i-1];
	for(int i=n;i>=1;i--)sa[c[x[i]]--]=i;
	//这三行表示第一次基数排序
	//再理解一下基数排序的过程:第一个循环记下每个第一关键字的数目,
	//第二个循环求前缀和,第三个循环记录排名
	for(int k=1;k<=n;k<<=1){
		int num=0;
		for(int i=n-k+1;i<=n;i++)y[++num]=i;
		for(int i=1;i<=n;i++)if(sa[i]>k)y[++num]=sa[i]-k;
		//取出第二关键字,后半段直接当作极小值,前半段用上一次的sa更新
		for(int i=1;i<=m;i++)c[i]=0;
		for(int i=1;i<=n;i++)c[x[i]]++;
		for(int i=2;i<=m;i++)c[i]+=c[i-1];
		for(int i=n;i>=1;i--){sa[c[x[y[i]]]--]=y[i];y[i]=0;}
		//这三行是基数排序,和前面类似
		swap(x,y);num=1;x[sa[1]]=1;
		for(int i=2;i<=n;i++){
			if(y[sa[i]]==y[sa[i-1]]&&y[sa[i]+k]==y[sa[i-1]+k])
				x[sa[i]]=num;//如果两个关键字都相同,那么排名也相同
			else x[sa[i]]=++num;
		}
		//求下一次的第一关键字
		if(num==n)break;m=num;
	}
	for(int i=1;i<=n;i++)printf("%d ",sa[i]);
	return 0;
}

应用:
一个小套路是字符串复制一遍接在后面。
更主要的应用需要联系 \(height\) 数组:\(height[i]=LCP(sa[i-1],sa[i])\),这里 \(sa\) 数组理解为字符串,LCP为最长公共前缀。
求 \(height\) 数组用到引理:\(height[rk[i]] \ge height[rk[i-1]]-1\),然后暴力即可。
Code:

void LCP(){
	int k=0;
	for(int i=1;i<=n;i++)rk[sa[i]]=i;//rk[i]表示i的排名
	for(int i=1;i<=n;i++){
		if(rk[i]==1)continue;if(k)--k;//至多减掉1,然后暴力向后找
		int j=sa[rk[i]-1];
		while(i+k<=n&&j+k<=n&&s[i+k]==s[j+k])++k;
		h[rk[i]]=k;
	}
}

后缀自动机(SAM):压缩一个字符串所有子串的一个自动机。

对任一结点,以它为终点的路径会构成一个子串,并且这些子串是其中最长那个的若干个连续后缀(例:aaaaba,aaaba,aaba,aba)。每个结点记:

  • maxlen,minlen:表示该点代表的最长字符串长和最短字符串长。
  • trans[]:转移函数。
  • slink:后缀链接。指向比这个点的最短字符串恰好短1的字符串(去掉开头,在上面的例子里就是ba)所在的结点。
  • endpos:该结点的所有字符串在原字符串中的出现位置的集合(记结尾)。可以理解为结点就是按照endpos拆分的。但是记集合肯定炸掉,所以实际上记的是集合大小。
  • pre:该点的最长字符串是否为原字符串的一个前缀。

构造算法:增量法。

  • 先给一个新点 \(z\),从代表整个(已完成的)字符串的点 \(u\) 开始,按照slink往前跳。如果相应的转移函数 \(trans[u][c]\) 为空,就指向加入的新点,然后接着跳。跳到自动机的起点,或不再满足条件时停止。如果跳到起点,那么新点 \(z\) 的minlen为1,slink指向起点。
  • 如果没有跳到起点,在停下的位置 \(v\) 检查 该点的对应转移函数 \(trans[v][c]\) 指向的结点 \(x\) 的最长字符串 是否为 停下结点 \(v\) 的最长字符串加上转移函数的字符(即长度是否恰好多1)。如果是,直接将 \(z\) 的slink指向 \(x\) 即可。
  • 剩下的情况,需要在 \(x\) 中拆出一部分 \(y\),让 \(v\) 指向 \(y\)。同时,让 \(v\) 沿着slink向前跳,满足 \(trans[v][c]==x\) 的都改为指向 \(y\)。至于 \(y\) 的参数,maxlen[y]=maxlen[v]+1,trans[y]=trans[x],slink[y]=slink[x],minlen[y]=maxlen[slink[y]]+1。以及minlen[x]=minlen[z]=maxlen[y]+1,slink[x]=slink[z]=slink[y]

下面来求endpos。显然slink会构成一棵树,而一个点 \(u\) 的endpos肯定包含了所有slink指向 \(u\) 的点的endpos。这可能不完全,但是会发现:只有当 \(pre[u]=1\) 时还需要再加一,否则不需要。那么可以构造完SAM后,自底向上做拓扑排序,递推即可。

Code:(洛谷模板P3804)

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+5;
char s[N];
int n,m,st,ans[N];long long res;
int deg[N];queue<int> Q;
int maxlen[N],minlen[N],trans[N][30],slink[N],pre[N],endpos[N];
int new_state(int _maxlen,int _minlen,int*_trans,int _slink,int _pre){
    maxlen[m]=_maxlen;minlen[m]=_minlen;
    for(int i=1;i<=26;i++){
        if(_trans==NULL)trans[m][i]=-1;
        else trans[m][i]=_trans[i];
	}
    slink[m]=_slink;pre[m]=_pre;
    return m++;
}
int add_char(char ch,int u){
	int c=ch-'a'+1,z=new_state(maxlen[u]+1,-1,NULL,-1,1),v=u;
	while(v!=-1&&trans[v][c]==-1){trans[v][c]=z;v=slink[v];}
	if(v==-1){minlen[z]=1;slink[z]=0;return z;}
	int x=trans[v][c];
	if(maxlen[v]+1==maxlen[x]){minlen[z]=maxlen[x]+1;slink[z]=x;return z;}
	int y=new_state(maxlen[v]+1,-1,trans[x],slink[x],0);
	minlen[x]=maxlen[y]+1;slink[x]=y;
	minlen[z]=maxlen[y]+1;slink[z]=y;
	while(v!=-1&&trans[v][c]==x){trans[v][c]=y;v=slink[v];}
	minlen[y]=maxlen[slink[y]]+1;
	return z;
}
int main(){
	scanf("%s",s+1);n=strlen(s+1);
	st=new_state(0,-1,NULL,-1,1);
	for(int i=1;i<=n;i++)st=add_char(s[i],st);--m;
	for(int i=1;i<=m;i++)deg[slink[i]]++;
	for(int i=1;i<=m;i++)if(!deg[i])Q.push(i);
	while(!Q.empty()){
		int u=Q.front();Q.pop();
		if(pre[u])endpos[u]++;
		endpos[slink[u]]+=endpos[u];
		deg[slink[u]]--;
		if(!deg[slink[u]])Q.push(slink[u]);
	}
	for(int i=1;i<=m;i++)ans[maxlen[i]]=max(ans[maxlen[i]],endpos[i]);
	for(int i=n-1;i>=1;i--){
		ans[i]=max(ans[i],ans[i+1]);
		if(ans[i]>=2)res=max(res,1ll*ans[i]*i);
	}
	printf("%lld\n",res);
	return 0;
}

标签:SAM,minlen,int,slink,maxlen,详解,字符串,SA,sa
From: https://www.cnblogs.com/by-chance/p/17535526.html

相关文章

  • sat计数问题
    /*先是建图,跑一次缩点建立一个最初的阵营加上0代表认识,1代表不认识ans=0因为划分了两个独立的阵营,所以一次是只能交换一个人的如果对方阵营我只认识一个,并且我认识的哪一个可以来到我的阵营,那么就将两者进行交换(0,1)如果对方阵营的我都不敌对,或不认识,那我就可以直接过......
  • Linux | curl命令详解
    curl是一个命令行访问URL的计算机逻辑语言的工具,发出网络请求,然后得到数据并提取出,显示在标准输出“stdout”上面,可以用它来构造httprequest报文,curl(CommandLineUniformResourceLocator),即在命令行中利用URL进行数据或者文件传输。在Linux中curl是一个利用URL规则在命令行......
  • HashMap的实现原理详解(看这篇就够了)
    一线资深java工程师明确了需要精通集合容器,尤其是今天我谈到的HashMap。HashMap在Java集合的重要性不亚于Volatile在并发编程的重要性(可见性与有序性)。我会重点讲解以下9点:1.HashMap的数据结构2.HashMap核心成员3.HashMapd的Node数组4.HashMap的数据存储5.HashMap的哈希函数6.哈......
  • HashMap的实现原理详解(看这篇就够了)
     一线资深java工程师明确了需要精通集合容器,尤其是今天我谈到的HashMap。HashMap在Java集合的重要性不亚于Volatile在并发编程的重要性(可见性与有序性)。我会重点讲解以下9点:1.HashMap的数据结构2.HashMap核心成员3.HashMapd的Node数组4.HashMap的数据存储5.HashMap......
  • BZOJ 3942: [Usaco2015 Feb]Censoring KMP
    3942:[Usaco2015Feb]CensoringTimeLimit: 10Sec  MemoryLimit: 128MBSubmit: 476  Solved: 260[Submit][Status][Discuss]DescriptionFarmerJohnhaspurchasedasubscriptiontoGoodHooveskeepingmagazineforhiscows,sotheyhaveplentyofmateria......
  • BZOJ 3402: [Usaco2009 Open]Hide and Seek 捉迷藏 最短路
    3402:[Usaco2009Open]HideandSeek捉迷藏TimeLimit: 3Sec  MemoryLimit: 128MBSubmit: 213  Solved: 167[Submit][Status][Discuss]Description    贝茜在和约翰玩一个“捉迷藏”的游戏.    她正要找出所有适合她躲藏的安全牛棚.一共有N(2≤N≤20000......
  • BZOJ 1725: [Usaco2006 Nov]Corn Fields牧场的安排 状压dp
    1725:[Usaco2006Nov]CornFields牧场的安排TimeLimit: 5Sec  MemoryLimit: 64MBSubmit: 698  Solved: 489[Submit][Status][Discuss]DescriptionFarmerJohn新买了一块长方形的牧场,这块牧场被划分成M列N行(1<=M<=12;1<=N<=12),每一格都是一块正方形的土地。FJ......
  • BZOJ 1915: [Usaco2010 Open]奶牛的跳格子游戏 单调队列优化dp
    1915:[Usaco2010Open]奶牛的跳格子游戏TimeLimit: 4Sec  MemoryLimit: 64MBSubmit: 281  Solved: 110[Submit][Status][Discuss]Description奶牛们正在回味童年,玩一个类似跳格子的游戏,在这个游戏里,奶牛们在草地上画了一行N个格子,(3<=N<=250,000),编号为1..N......
  • 微信小程序开发-wx.saveFile把文件下载到哪里
    我们在使用微信小程序的API时wx.saveFile(OBJECT)一、电脑中可能大家要看一看使用小程序开发工具,具体把文件下载到了我们计算机的什么地方,以win10为例,下载到了如下路径:C:\Users\cuanboy\AppData\Local\微信web开发者工具\UserData\例如我保存了一个20210419.csv文件到电脑中......
  • 2023ACM暑假训练day 9 后缀自动机SAM
    目录DAY9后缀自动机SAM训练情况简介题DAY9后缀自动机SAM训练情况简介2023-07-0709:20:38星期五题题意:思路:......