后缀自动机学习

今天终于把这周的坑填了,同样看了很多博客,这里就不详细总结了,就简单整理一下了。

应用:

1、存在性查询:给定文本T,询问格式如下:给定字符串P,问P是否是T的子串。 直接按着路径走,看是否存在即可

2、不同的子串个数:对于每一个节点即为:len[i] - len[fa[i]] 加和即可

3、不同子串的总长:这里我是通过len[i] 和 fa[] 来求得

4、字典序第k小子串:先保存在每个节点后继的字符串的数目,然后逐步找即可,链接:点击查看

5、最小循环移位:这个还没整理

问题.给定字符串S,找到和它循环同构的字典序最小字符串。

 复杂度要求.O(length(S)).

算法.我们将字符串S+S建立后缀自动机。该自动机将包含和S循环同构的所有字符串。

 从而,问题就简化成了在自动机中找出字典序最小的,长度为length(S)的路径,这很简单:从初始状态开始,每一步都贪心地走

,经过最小的转移。

6、出现次数查询:按照路径找,num即为次数

7、首次出现位置查询:可以在开一个数组,在插入的时候直接记录即可

8、所有出现位置查询:讲解:https://blog.csdn.net/qq_35649707/article/details/66473069

9、查询不在文本中出现的最短字符串

10、求两个字符串的最长公共子串:for查询串,通过fa回溯查找,链接:点击查看

多个串的:点击查看

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;

struct SAM {
	int n; // 字符串长度 
	int tot; // 节点编号 
	int len[N << 1]; // 最长子串的长度 (该节点子串数量 = len[x] - len[fa[x]])
	int fa[N << 1]; // // 后缀链接 (最短串前部少一个字符所到达的状态) 
	int cnt[N << 1]; // 被后缀链接的个数
	int nex[N << 1][26]; // 下一个状态
	int last; // 最后一个节点
	ll num[N << 1]; // 该状态子串的数量
	ll maxx[N << 1]; // 长度为x的子串出现次数最多的子串的数目 
	ll sum[N << 1]; // 该节点后面所形成的自字符串的总数 
	ll ans; // 不同字符串数目 
	ll sublen; // 不同字符串总长度 
	int X[N << 1], Y[N << 1]; // Y表示排名为x的节点,X表示该长度前面还有多少个 
	void init(int x) {
		for(int i = 0; i <= x * 2 + 10; i++) {
			maxx[i] = 0;
			cnt[i] = 0;
			num[i] = 0;
			X[i] = 0; 
			for(int j = 0; j <= 25; j++)
				nex[i][j] = 0;
		}
		n = 0; 
		last = 1;
		tot = 1; // 1是起始点 空集 
		fa[1] = 0;
		len[1] = 0;
		
	} 
	void insert (int c) { 
		n++;
		int x = ++tot; // 新建节点 
		len[x] = len[last] + 1;
		num[x] = 1;
		int p;
		for(p = last; p && !nex[p][c]; p = fa[p]) nex[p][c] = x; //沿着后缀连接 将所有没有字符c转移的节点直接指向新节点
		if(!p) fa[x] = 1, cnt[1]++; // 没有c的转移,直接链接到起点 
		else {
			int q = nex[p][c];
			if(len[p] + 1 == len[q]) // p q连续 
				fa[x] = q, cnt[q]++;
			else {
				int nq = ++tot; // 不连续 复制一份q的 使得和p连续 
				len[nq] = len[p] + 1;
				fa[nq] = fa[q];
				memcpy(nex[nq], nex[q], sizeof(nex[q]));
				for(; p && nex[p][c] == q; p = fa[p]) nex[p][c] = nq; //沿着后缀连接 将所有通过c转移为q的改为nq
				fa[q] = fa[x] = nq;
				cnt[nq] += 2;
			}
		}
		last = x;
	}
	void getposnum() { 
		for(int i = 1; i <= tot; i++) X[len[i]]++;
		for(int i = 1; i <= tot; i++) X[i] += X[i - 1];
		for(int i = 1; i <= tot; i++) Y[X[len[i]]--] = i;
		for(int i = tot; i >= 1; i--) num[fa[Y[i]]] += num[Y[i]];
	}
	void getSUM() {
		for(int i = tot;i >= 1; i--) {
			sum[Y[i]] = 1;
			for(int j = 0 ;j <= 25; j++)
				sum[Y[i]] += sum[nex[Y[i]][j]];
		}		
	}
	void getsubnum() { // 不同字符串数目 
		ans = 0;
		for(int i = 1; i <= tot; i++)
			ans += len[i] - len[fa[i]]; 
	} 
	void getmaxx() { // 长度为x的子串出现次数最多的子串的数目 
		// getposnum();
		for(int i = 1; i <= tot; i++) maxx[len[i]] = max(maxx[len[i]], num[i]);
		for(int i = n - 1; i >= 1; i--) maxx[i] = max(maxx[i], maxx[i + 1]);
	} 

    void getsublen() {
		sublen = 0;
		for(int i = 1; i <= tot; i++) {
			sublen += 1LL * (len[i] + len[fa[i]] + 1) * (len[i] - len[fa[i]]) / 2; 
		}
		//	cout << sublen << endl;
	}
	// 两个串最长公共子串 
	int compare(char str[]) {
		int l = strlen(str);
		int res = 0, cnt = 0;
		for(int i = 0, p = 1; i < l; i++) {
			if(nex[p][str[i] - 'a']) cnt++, p = nex[p][str[i] - 'a'];
			else {
				while(p && !nex[p][str[i] - 'a']) p = fa[p];
				if(!p) cnt = 0, p = 1;
				else cnt = len[p] + 1, p = nex[p][str[i] - 'a'];
			}
			res = max(res, cnt);
		}
	//	printf("%d\n", res);
		return ans;
	}
	/* 多个串 
	int compare(char str[]) {
		int l = strlen(str);
		int res = 0, cnt = 0;
		for(int i = 0, p = 1; i < l; i++) {
			if(nex[p][str[i] - 'a']) cnt++, p = nex[p][str[i] - 'a'];
			else {
				while(p && !nex[p][str[i] - 'a']) p = fa[p];
				if(!p) cnt = 0, p = 1;
				else cnt = len[p] + 1, p = nex[p][str[i] - 'a'];
			}
			dp[p] = max(dp[p], cnt);
		}
		for(int i = tot; i >= 2; i--)
			dp[fa[i]] = max(dp[fa[i]], min(dp[i], len[fa[i]]));
		for(int i = 2; i <= tot; i++) {
			if(cntans[i] == -1 || dp[i] < cntans[i])
				cntans[i] = dp[i];
			dp[i] = 0;
		}
			
		return res;
	}
	*/
	void get_kth(int k) {
		int pos = 1, cnt;
		string s = "";
		while(k) {
			for(int i = 0; i <= 25; i++) {
				if(nex[pos][i] && k) {
					cnt = nex[pos][i];
					if(sum[cnt] < k) k -= sum[cnt];
					else {
						k--;
						pos = cnt;
						s += (char)(i + 'a');
						break;
					}
				}
			}
		}
		cout << s << endl;
	}
}sam;
char s[N];
int main() {
	
	int T, len;
	scanf("%s", s + 1);
	len = strlen(s + 1);
	sam.init(len);
	for(int i = 1; i <= len; i++)
		sam.insert(s[i] - 'a');
	sam.getposnum();
	sam.getSUM();
	
	return 0;
}

推荐博客:

俄译,很详细:https://blog.csdn.net/qq_35649707/article/details/66473069

推荐模板(但我感觉我整理的更好了。。。):https://blog.csdn.net/Anoy_acer/article/details/82932524

学弟鑫爷的各种题型整理:https://blog.csdn.net/zhaoxinxin1234/article/details/97886634#commentBox

有的还没有过题,如有错误,敬请批证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值