Manacher、SA总结

首先是比较水的Manacher

用于求最长回文子串
名字很牛叉的样纸,实际上相当水。
首先在每两个字符间插入‘#’字符(和网上的方法不一样,因为两端实际上要插入不用的字符来使while停止)
于是我们可以只需考虑求长度为奇数的回文串
记P[i]为向右最长拓展到的元素(此处包括了‘#’字符)(也就是以i为中心的最长回文串长度, 不包括‘#’字符)
考虑如何通过p[1] .. p[i - 1] 推出 p[i]
考虑在求p[i]的同时维护p中最大元素pmax和其位置pos
下面上代码。因为实在不想讲,也讲不清..不如代码简洁暴力。

#include<cstdio>
#define min(a, b) ({int _ = a, __ = b; _ < __ ? _ : __;})
#define ot "%d"
#define maxn 1005


using namespace std;


int p[maxn], n;
char s[maxn], c;


int manacher()
{
	int pos, pmax = 0;
	for (int i = 1; i < n; ++i)
	{
		if (pmax > i) p[i] = min(pmax - i, p[(pos << 1) - i]); else p[i] = 1;
		while (s[i - p[i]] == s[i + p[i]]) ++p[i];
		if (p[i] + i > pmax) pmax = p[i] + i, pos = i;
	}
	return pmax;
}


int main()
{
	n = 0;
	freopen("test.in", "r", stdin);
	freopen("test.out", "w", stdout);
	while ((c = getchar()) != '\n') s[++n] = c, s[++n] = '#';
	s[0] = '@'; s[n] = '$'; --n; //此处假设字符集为大小写字母或数字
	printf(ot, manacher());
	return 0;
}

肯定有人想问为什么是O(n)的。

事(系)实(际)上,注意到while() 的执行总次数是O(n)的。(呵呵...真NC)不证了,1是因为网上到处有,2是想想就明白了...



那么就是这样了。


接下来是SA。

【郑重提示:千万别看论文的代码!可读性实在是相当那啥....TAT(或者说我太弱了?),看了半天才看懂】


Suffix Array 这个东西嘛..顾名思义和后缀有关...网上各种介绍很多。基础的就不说了。
主要说说倍增是怎么回事。网上说的太不清楚了。
首先我们要知道桶排的原理。
比如:
给定{An},将其排序后输出。(n ≤ 10 ^ 7)(保证0 ≤ Ai ≤ 10 ^ 7)
怎么做呢?qsort超时无悬念啊。
我们开一个b数组,b[i] 表示 值为 i 的数字 在{An} 中出现了多少次。
显然O(n)求出。
然后我们就可以利用B输出了。(好2B...)这就是桶排。
基数排序则可以看做几次桶排。知道桶排基数排序应该就很好理解了。
那么我们如何利用基数排序呢?
神奇的 倍增出现了!
还是那张老图。


怎么理解呢?
我们记 suffix(s, i) 为s的第i长的后缀(即从第i个字符开始的后缀)
记 prefix(s, i) 为s的第i长的前缀(即到第i个字符结尾的前缀)
对于那么 对于给定的k(k ≥ 1),将所有形如prefix(suffix(s, i), 2^k)的字符串(后缀的前缀好纠结..)排序
我们可以得出一个临时键值(rank)。如何得出呢?
首先 rank(prefix(suffix(s, i), 1)) 可以直接得出。(直接是AscII码就行了)
然后我们假设已经知道 prefix(suffix(x, i), 2 ^ (k - 1)) , prefix(suffix(x, i +  2 ^ (k - 1)), 2 ^ (k - 1)) 两个数
以这两个数作为信息,将其基数排序即可得出新的rank。
(好像说的还是不是很清楚...ORZ.....)
那么就可以求出来了嘛...
但是有什么用呢。
我们 又引入了两个个数组:height, h(真恶心啊..这么多数组),
令height[i]=LCP(suffix(sa[i-1]), suffix(sa[i])), h[i]=height[Rank[i]], (height[i]=h[SA[i]])
也就是h[i] 表示 排名为i的后缀和在其前一名的后缀的最长公共前缀。
然后有一个神奇的性质:h[i] >= h[i-1]-1
这是为什么捏?
我们不妨设suffix(k)是排在suffix(i-1)前一名的后缀,则它们的最长公共前缀是h[i-1]。
那么suffix(k+1)将排在suffix(i)的前面(这里要求h[i-1]>1,如果h[i-1]≤1,原式显然成立)
并且suffix(k+1)和suffix(i)的最长公共前缀是h[i-1]-1。
所以suffix(i)和在它前一名的后缀的最长公共前缀至少是h[i-1]-1。

按照h[1],h[2],……,h[n]的顺序计算,并利用h 数组的性质,时间复杂度可以降为O(n)。

那么就是这样了。

关于代码:如果想理解的话..请看这个。

http://hi.baidu.com/pianoeater/item/7b08ef60bf77063869105b9d


至于我写的. 这是个裸的...不过看还是看得过去吧。

有些地方应该更好理解。

#include<cstdio>
#include<cstring>
#include<climits>
#define maxlen 1005
#define FOR(i, a, b) for (int i = a; i <= b; ++i)
#define ROF(i, b, a) for (int i = b; i >= a; --i)
#define ms(a, b) memset((a), (b), sizeof (a))

int n, len, bk[maxlen], x[maxlen], y[maxlen], sa[maxlen], rank[maxlen], a[maxlen], h[maxlen];
char s[maxlen];

using namespace std;

void work_sa()
{
	ms(bk, 0); //bk : bucket
	FOR (i, 1, len) bk[rank[i] = a[i]]++;
	FOR (i, 1, n) bk[i] += bk[i-1];
	ROF (i, len, 1) sa[bk[rank[i]]--] = i;	//Initialization
	for (int q = 1, p, m; p < len; q <<= 1, m = p)
	{
		p = 0; // p means the max rank, also does m
		FOR (i, len - q + 1, len) y[++p] = i; // Well, actually I don't understand it .... TAT
		FOR (i, 1, len) if (sa[i] > q) y[++p] = sa[i] - q;
		FOR (i, 1, len) x[i] = rank[y[i]];
		ms(bk, 0);
		FOR (i, 1, len) bk[x[i]]++;
		FOR (i, 1, n) bk[i] += bk[i-1];
		ROF (i, len, 1) sa[bk[x[i]]--] = y[i];
		memcpy(y, rank, sizeof y);
		rank[sa[1]] = 1, p = 1;
		FOR (i, 2, len)
			rank[sa[i]] = ((y[sa[i]] == y[sa[i - 1]]) && (y[sa[i] + q] == y[sa[i - 1] + q])) ? p : ++p;
	}
}

void work_h()
{
	int k = 0, j;
	for (int i = 1; i <= len; h[rank[i++]] = k)
		for (k ? --k : 0, j = sa[rank[i] - 1]; a[i + k] == a[j + k]; ++k);
}

int main()
{
	gets(s + 1); len = strlen(s + 1); int l = INT_MAX; n = 0;
	FOR (i, 1, len) a[i] = (int)s[i], l <?= a[i], n >?= a[i];
	n -= --l; FOR (i, 1, len) a[i] -= l;
	work_sa();
	work_h();
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值