SAM_后缀自动机笔记(图文加注释)_(Luogu P3804)

73 篇文章 0 订阅

我是看洛谷这篇文章(https://www.luogu.com.cn/blog/Kesdiael3/hou-zhui-zi-dong-ji-yang-xie)学习的,建议也看看那篇文章先。
这里我画了些图供补充理解。

提供几个便于理解的字符串:“bcaca” “aaaaa” “aababa” “aabcabc”

下面还有我带注释的代码,要是有错请提出来,我也刚学会 😃

在这里插入图片描述
关于case2 那个添加复制节点我有个图来理解
在这里插入图片描述

在这里插入图片描述

要是空格缩进不支持的话,附上paste.ubuntu链接 https://paste.ubuntu.com/p/hvJSFNdtZ2/
该代码可提交通过luogu这道模板题,下面注释的那一串是输出自动机的图。
https://www.luogu.com.cn/problem/P3804

#include <cstdio>
#include <cstring>
#include <algorithm>
#define Add(x,y) nxt[++num]=head[x],v[num]=(y),head[x]=num
#define For(x) for (int h=head[x],o=v[h]; h; o=v[h=nxt[h]])
using namespace std;
const int N=1000005;
char S[N];
int L;
int head[2*N],nxt[2*N],v[2*N],num;

struct node
{
	int ch[26],len,fa;
/*
ch[c]	自动机中该状态的 c 出边 
len		parenttree 中该 endpos 类中最长串的长度 
fa		parenttree 中该节点的父节点 
*/
};



node a[2*N];
int tot,lst,cnt[2*N];
struct _SAM
{
	
/*
tot		当前节点数
lst		旧串(加入c之前)对应的状态节点 
*/
	//_SAM(){tot=lst=1; memset(a,0,sizeof(a)); memset(cnt,0,sizeof(cnt)); ;}
	void reset()
	{
		tot=lst=1;
		memset(a,0,sizeof(a));
		memset(cnt,0,sizeof(cnt));
	}
	
	void insert(int c)
	{
		int p=lst,newp=++tot;
/*
p		旧串的某个后缀 
np		新节点,代表新串本身 
*/
		
		a[newp].len=a[lst].len+1;	//新节点状态代表加入 c 后整个串 
		while (p && !a[p].ch[c])	//一直跳旧串的后缀, 直到 p 有 c 边 
		{
			a[p].ch[c]=newp;			//将没有 c 边的旧串后缀节点加上 c 边 
			p=a[p].fa;
		}
		
		if (!p)							//#1
			a[newp].fa=1;
		else
		{
			int q=a[p].ch[c];
			if (a[p].len+1==a[q].len)	//#2.1 
				a[newp].fa=q;
			else						//#2.2
			{
				int newq=++tot;			//"复制" 一个 q 
				a[newq]=a[q];
				a[newq].len=a[p].len+1;
				a[q].fa=a[newp].fa=newq;
				
				while (p && a[p].ch[c]==q)	//调整 newq 使之成为相当于 #2.1 中 q 的角色 
				{
					a[p].ch[c]=newq;
					p=a[p].fa;
				}				
			}
		}
		lst=newp;
	}
/*	
	#1
		意味着当前 c 是个新出现的字符 
	
	#2.1
		若 q.len==p.len+1, 
		则 q.longest 刚好是 p.longest 后面加 c, 
		说明 q 中所有状态都是新串的后缀, 
		newp.fa 可为 q
		
	#2.2
		若 q.len!=p.len+1, 
		则 q.longest 是 p.longest 后面加 c, 而且前面还要加一些字符, 
		说明需要新建一个节点 newq, 充当 #2.1 中的 q 的角色 
*/ 

	void get_cnt()
	{
		memset(head,0,sizeof(head));
		num=0;
		for (int i=1; i<=tot; i++)
			Add(a[i].len,i);
		for (int i=0,tmp=1; i<L; i++)
			cnt[tmp=a[tmp].ch[(int)S[i]-'a']]=1;
		for (int i=L; i; i--) For(i)
			cnt[a[o].fa]+=cnt[o];
	}
} SAM;


int main()
{
	long long ans=0;
	scanf("%s",S);
	L=strlen(S);
	SAM.reset();
	for (int i=0; i<L; i++)
		SAM.insert(S[i]-'a');
	SAM.get_cnt();
	for (int i=1; i<=tot; i++)
		if (cnt[i]>1 && a[i].len*cnt[i]>ans)
			ans=a[i].len*cnt[i];
	printf("%lld",ans);

/*
	int L;
	char S[10000];
	scanf("%s",S);
	L=strlen(S);
	SAM.reset();
	for (int i=0; i<L; i++)
		SAM.insert(S[i]);
	for (int i=1; i<=SAM.tot; i++)
	{
		printf("[%d] ->\t%d\n\t\t",i,SAM.a[i].fa);
		for (int j=1; j<=100; j++)
			if (SAM.a[i].ch[j])
				printf("(%c) %d\n\t\t",j,SAM.a[i].ch[j]);
		printf("\n\n");
	}
*/

	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值