/*
poj2774
题意:
给两个字符串(长度不超过100000),求最长公共子串
用我学过的平常的暴力或kmp的方法都会超时,所以刚好可以用来
当做后缀数组的入门题
在下边借用一些论文里的知识再加上自己的一些理解
一、后缀数组的实现
后缀:就是从某个位置i开始到整个字符串末尾的特殊的字符串,用suffix(i)表示
大小比较:关于字符串的大小比较,是指通常所说的“字典顺序”比较,也
就是对于两个字符串u、v,令i 从1 开始顺次比较u[i]和v[i],如果
u[i]=v[i]则令i 加1,否则若u[i]<v[i]则认为u<v,u[i]>v[i]则认为u>v
(也就是v<u),比较结束。如果i>len(u)或者i>len(v)仍比较不出结果,那
么若len(u)<len(v) 则认为u<v , 若len(u)=len(v) 则认为u=v , 若
len(u)>len(v)则u>v。
后缀数组:后缀数组SA 是一个一维数组,它保存1..n 的某个排列SA[1],
SA[2],……,SA[n],并且保证Suffix(SA[i]) < Suffix(SA[i+1]),1≤i<n。
也就是将S 的n 个后缀从小到大进行排序之后把排好序的后缀的开头位置顺
次放入SA 中。
名次数组:名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排
列的“名次”。
简单的说,后缀数组是“排第几的是谁?”,名次数组是“你排第几?”。
求后缀数组与名次数组的方法
有2路倍增算法和3DC3算法两种,第一种方法,容易实现容易理解,但是没第二种方法更高效
height 数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公
共前缀,也就是排名相邻的两个后缀的最长公共前缀。
定义h[i]=height[rank[i]],也就是suffix(i)和在它前一名的后缀的最长公共前缀。
h 数组有以下性质:h[i]≥h[i-1]-1 证明省略,感兴趣可以看论文,所以按照
h[1],h[2]...h[n]的顺序求height[]会是时间上得到优化
后缀数组的好多应用都和sa[],height[],rank[]有关
对本题求最长公共子串,即可把两个字符串合并,求height得最大值
是要考虑公共前缀必须来自不同的字符串,所以要在中间加上一个特殊字符,
并且判断是否来自不同的字符串
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <cmath>
#include <algorithm>
typedef long long ll;
using namespace std;
const int maxn = 200000 + 5;
int wa[maxn], wb[maxn], wv[maxn], wsf[maxn];
int sa[maxn], Rank[maxn], height[maxn], s[maxn];
char str1[maxn], str2[maxn];
int cmp(int *r, int a, int b, int l)
{
return r[a] == r[b] && r[a+l] == r[b+l];
}
void getsa(int *r, int *sa, int n, int m)//在字符串后边加上0,让其代表的后缀排名为0
{ //有比较时不越界的用处
int *x = wa, *y = wb, *t;
/*二路倍增算法是对每个字符开始的长度为2的k次方的子字符串进行排序,
求出排名,就是rank值,当2的k次方大于n时就对所有的后缀排好了序
每一次排序都可以利用上次排好的2的k-1次方的结论来优化,那么长为
2的k次方的字符串就可以用两个2的k-1次方的rank值来表示,对两位从低到高
进行基数排序即可
*/
//首先对长为1的字符串进行基数排序,wsf存储的是相当于前缀和
//x[]数组相当于临时的rank[]数组
for (int i = 0; i < m; i++) wsf[i] = 0;//每次分配前清空计数器
for (int i = 0; i < n; i++) wsf[x[i]=r[i]]++;//统计每个桶中的记录数
for (int i = 1; i < m; i++) wsf[i] += wsf[i-1];//将x[]中的位置依次分配给每个桶
for (int i = n-1; i >= 0; i--) sa[--wsf[x[i]]] = i;//将所有桶中记录依次收集到sa中
for (int j = 1, p = 1; p < n; j*=2, m = p)
{
//cout << "kkk" << j << endl;
//第一次基数排序时,用到上次sa中的结果,y数组存下第一次基数排序的结果
int i = n - j;
for (p = 0; i < n; i++) y[p++] = i;//后边的要补上0,所以直接是排名最前的
for (i = 0; i < n; i++) if (sa[i] >= j) y[p++] = sa[i]-j;
for (i = 0; i < n; i++) wv[i] = x[y[i]];//第二次基数排序
for (i = 0; i < m; i++) wsf[i] = 0;
for (i = 0; i < n; i++) wsf[wv[i]]++;
for (i = 1; i < m; i++) wsf[i] += wsf[i-1];
for (i = n-1; i >= 0; i--) sa[--wsf[wv[i]]] = y[i];
//x和y数组进行交换,y存下第二次排序后的结果
//然后求得临时的rank[]数组,p为排的的名次,这里进行一个优化,
//如果p<n说明排名有相同的则要继续进行比较
for (t = x, x = y, y = t, x[sa[0]] = 0, i = 1, p = 1; i < n; i++)
{
x[sa[i]] = cmp(y, sa[i-1], sa[i], j)? p-1 : p++;
}
}
return;
}
void getheight(int *r, int n)//字符串后边不加0
{
//h[1],h[2]...h[n]的顺序求height[]
//这里赋值Rank[]从1开始,防止下边-1操作越界
//且求height[1]时刚好和最后的字符串后的0比较
//也是求height字符串后不加0的的巧妙之处
for (int i = 1; i <= n; i++) Rank[sa[i]] = i;
int k = 0;
for (int i = 0; i < n; i++)
{
if (k)k--;
else k = 0;
int j = sa[Rank[i]-1];//suffix(i)前一名的后缀字符串的起始位置
while(r[i+k] == r[j+k])k++;
height[Rank[i]] = k;
}
}
int main()
{
//freopen("input.txt", "r", stdin);
while(scanf("%s%s", str1, str2) == 2)
{
//puts(str1);
//puts(str2);
int n = 0;
int len = strlen(str1);
for (int i = 0; i < len; i++)
s[n++] = str1[i]-'a'+1;
s[n++] = 30;
len = strlen(str2);
for (int i = 0; i < len; i++)
s[n++] = str2[i]-'a'+1;
s[n] = 0;
getsa(s, sa, n+1, 31);
getheight(s, n);
int ans = 0;
len = strlen(str1);
for (int i = 2; i <= n; i++)
{
if (height[i] > ans)
{
//判断起始位置即可
if (sa[i-1] >= 0 && ((sa[i-1] < len && sa[i] >= len) ||
(sa[i] < len && sa[i-1] >= len)))
ans = height[i];
}
}
cout << ans << endl;
}
return 0;
}
poj2774 后缀数组
最新推荐文章于 2019-01-10 11:27:35 发布