后缀自动机小结 (spoj 8222)

后缀自动机理解关键点:

1. 根到任意一个结点都可以形成S的一个子串,并且S的所有子串都可以通过这种方式形成;

2. 到达该节点是所有路径就是一个right集合,一个拥有相同后缀的right集合;

3. 设某子串为str,这后缀自动机读入str后能到达的状态为right(str),即str在S中出现的位置的集合;

4. 假设node[b].fa = a,则状态a可以代替状态b进行识别。

附图:

更详细的资料:

http://wenku.baidu.com/view/90f22eec551810a6f4248606.html

http://blog.sina.com.cn/s/blog_7812e98601012cim.html

例题:

spoj 8222

题意:给一个字符串S,令F(x)表示S的所有长度为x的子串中,出现次数的最大值。求F(1)..F(Length(S))。

限制:|S| <= 250000

/*spoj 8222
  题意:
  给一个字符串S,令F(x)表示S的所有长度为x的子串中,出现次数的最大值。求F(1)..F(Length(S))。
  限制:
  |S| <= 250000
  思路:
  后缀自动机
  后缀自动机理解关键点:
  1. 根到任意一个结点都可以形成S的一个子串,并且S的所有子串都可以通过这种方式形成;
  2. 到达该节点是所有路径就是一个right集合,一个拥有相同后缀的right集合;
  3. 设某子串为str,这后缀自动机读入str后能到达的状态为right(str),即str在S中出现的位置的集合;
  4. 假设node[b].fa = a,则状态a可以代替状态b进行识别。
 */
#include <iostream>
#include <cstdio>
#include <cstring>
#include <queue>
using namespace std;

const int N = 250005;

//注意,后缀自动机的节点要开2倍n,根是从1开始的
struct SAM {
	struct Node {
		int fa, ch[27];
		int val, ans;
		void init() {
			fa = 0;
			memset(ch, 0, sizeof(ch));
			val = 0;
			ans = 0;
		}
	} node[2 * N];

	int tot;
	int new_node() {
		node[++tot].init();
		return tot;
	}

	int root, last;
	void init() {
		tot = 1;
		root = last = 1;
		node[0].init();
		node[root].init();
	}
	void add(int x) {
		int p = last;
		int np = new_node(); node[np].val = node[p].val + 1;
		while(p && node[p].ch[x] == 0)
			node[p].ch[x] = np, p = node[p].fa;
		if(p == 0)
			node[np].fa = root;
		else {
			int q = node[p].ch[x];
			if(node[p].val + 1 == node[q].val)
				node[np].fa = q;
			else {
				int nq = new_node(); node[nq].val = node[p].val + 1;
				memcpy(node[nq].ch, node[q].ch, sizeof(node[nq].ch));
				node[nq].fa = node[q].fa;
				node[q].fa = node[np].fa = nq;
				while(p && node[p].ch[x] == q)
					node[p].ch[x] = nq, p = node[p].fa;
			}
		}
		last = np;
	}
	void debug() {
		for(int i = 1; i <= tot; ++i) {
			printf("id=%d, fa=%d, step=%d, ch=[ ", i, node[i].fa, node[i].val);
			for(int j = 0; j < 26; ++j) {
				if(node[i].ch[j])
					printf("%c,%d ", j+'a', node[i].ch[j]);
			}
			puts("]");
		}
	}
	void gao(int);
}sam;

char str[N];

int du[2 * N];
int cnt[2 * N];
int ans[N];

void init() {
	sam.init();
	memset(du, 0, sizeof(du));
	memset(ans, 0, sizeof(ans));
	memset(cnt, 0, sizeof(cnt));
}

int que[2 * N], fr, ta;

void SAM::gao(int n) {
	for(int i = 1; i <= tot; ++i) {
		++du[node[i].fa];
	}
	int tmp = root;
	for(int i = 0; i < n; ++i) {
		tmp = node[tmp].ch[str[i] - 'a'];
		cnt[tmp] = 1;
	}
	fr = ta = 0;
	for(int i = 1; i <= tot; ++i) {
		if(du[i] == 0) {
			que[ta++] = i;
		}
	}
	while(fr != ta) {
		int now = que[fr++];
		--du[node[now].fa];
		cnt[node[now].fa] += cnt[now];
		if(du[node[now].fa] == 0) {
			que[ta++] = node[now].fa;
		}
	}

	for(int i = 2; i <= tot; ++i) {
		ans[node[i].val] = max(ans[node[i].val], cnt[i]);
	}
	for(int i = n - 1; i >= 1; --i) {
		ans[i] = max(ans[i], ans[i + 1]);
	}
	for(int i = 1; i <= n; ++i) {
		printf("%d\n", ans[i]);
	}
}

int main() {
	while(gets(str)) {
		int n = strlen(str);
		init();
		for(int i = 0; i < n; ++i)
			sam.add(str[i] - 'a');
		//sam.debug();
		sam.gao(n);
	}
	return 0;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-09-27 00:05:22

后缀自动机小结 (spoj 8222)的相关文章

后缀自动机小结

后缀自动机小结 太神仙了学不来 由于每次写SAM的题都感觉是一次升华,于是决定好好的捋一捋SAM的相关知识,也许下面的东西并不是很清楚(毕竟我还是有点迷糊),欢迎指正! 定义 先介绍自动机 自动机(有限状态自动机),它的功能就是识别一个字符串,对于一个自动机\(A\),若它能识别一个字符串\(str\),则\(A(str)=true\),否则\(A(str)=false\) 自动机有五个重要的组成部分: 字符集:\(alpha\) 状态集合:\(state\) 初始状态集合:\(init\)(如

【后缀自动机】SPOJ 8222-最多重复子串

题意: 一个长度不超过250000的字符串,求出它长度为i的子串最多出现了多少次. 后缀自动机练习题...虽说是用Cube评测的不过时限仍然鬼畜. 考虑SAM的性质: 一个串的SAM肯定可以接受这个串的所有子串.SAM上的每一个点代表了一个子串. 主链:SAM上最长的那条链,也就是说从根走到主链的尾端就是整个字符串. 用t[i]记录i号点表示的子串的出现次数,沿着主链走一遍,很明显,主链上所有的点都代表了一个前缀,每个前缀都出现过.因此主链上的点的t初始值为1. 接着,之前提到过,一个点的pre

【后缀自动机】SPOJ 1812-LCSII

题意: 给出最多10个长度不超过100000的字符串,求他们的LCS的长度.时限是鬼畜的0.25s . 后缀自动机练习...虽然有人这么说但我并不觉得hash能过. 本题可以说是[论SAM中按step排序更新pre的重要性]: 总的来说做法和1811-LCS有点类似,不同的是因为有多个字符串,因此每个字符串都需要在SAM上跑一次. 记录下每个节点最长能容纳长度为多少的字符,最后取个MAX就行了. 用nans[i]表示匹配当前字符串时,i号点能容纳的子串长度,如果i的pre上的当前答案比i还要小的

【SPOJ】8222. Substrings(后缀自动机)

http://www.spoj.com/problems/NSUBSTR/ 题意:给一个字符串S,令F(x)表示S的所有长度为x的子串中,出现次数的最大值.求F(1)..F(Length(S)) 这题做法: 首先建立字符串的后缀自动机. 因为自动机中的每个状态都代表一类子串前缀,且任意状态的最长的|max|所对应的子串是唯一的. 所以我们算出每个子串(即找到的状态是end态),他们的right值为++(即保证长度为当前子串的出现次数为1),然后自底向上在parent树中更新right值(pare

【SPOJ】7258. Lexicographical Substring Search(后缀自动机)

http://www.spoj.com/problems/SUBLEX/ 后缀自动机系列完成QAQ...撒花..明天or今晚写个小结? 首先得知道:后缀自动机中,root出发到任意一个状态的路径对应一个子串,而且不重复.(原因似乎是逆序后缀树? 所以我们在自动机上预处理每一个状态的子串数目,然后从小到大枚举字符. 子串数目可以这样预处理出:s[x]=sum{s[y]}+1, y是x出发的下一个点,意思就是说,以x开头的子串有那么多个(即将孩子的所有子串前边都加上x),然后x单独算一个子串. 然后

SPOJ 1812 Longest Common Substring II(后缀自动机)

[题目链接] http://www.spoj.com/problems/LCS2/ [题目大意] 求n个串的最长公共子串 [题解] 对一个串建立后缀自动机,剩余的串在上面跑,保存匹配每个状态的最小值, 取最小值中的最大值即可.由于跑的地方只记录了匹配结尾的状态, 所以还需要更新parent树上的状态,既然匹配到了子节点, 那么parent树链上的值就都能够取到l, 一开始给每个不同状态按照l从小到大分配储存地址, 这样,我们就可以从匹配长度最长的开始更新parent树的情况. [代码] #inc

spoj 1811 LCS - Longest Common Substring (后缀自动机)

spoj 1811 LCS - Longest Common Substring 题意: 给出两个串S, T, 求最长公共子串. 限制: |S|, |T| <= 1e5 思路: dp O(n^2) 铁定超时 后缀数组 O(nlog(n)) 在spoj上没试过,感觉也会被卡掉 后缀自动机 O(n) 我们考虑用SAM读入字符串B; 令当前状态为s,同时最大匹配长度为len; 我们读入字符x.如果s有标号为x的边,那么s=trans(s,x),len = len+1; 否则我们找到s的第一个祖先a,它

spoj 1812 LCS2 - Longest Common Substring II (后缀自动机)

spoj 1812 LCS2 - Longest Common Substring II 题意: 给出最多n个字符串A[1], ..., A[n], 求这n个字符串的最长公共子串. 限制: 1 <= n <= 10 |A[i]| <= 1e5 思路: 和spoj 1811 LCS差不多的做法 把其中一个A建后缀自动机 考虑一个状态s, 如果A之外的其他串对它的匹配长度分别是a[1], a[2], ..., a[n - 1], 那么min(a[1], a[2], ..., a[n - 1]

【SPOJ】Longest Common Substring II (后缀自动机)

[SPOJ]Longest Common Substring II (后缀自动机) 题面 Vjudge 题意:求若干个串的最长公共子串 题解 对于某一个串构建\(SAM\) 每个串依次进行匹配 同时记录\(f[i]\)表示走到了\(i\)节点 能够匹配上的最长公共子串的长度 当然,每个串的\(f[i]\)可以更新\(f[i.parent]\) 所以需要拓扑排序 对于每个串求出每个节点的最长匹配 然后对他们取\(min\),表示某个节点大家都能匹配的最长长度 最后对于所有点的值都取个\(max\)