POJ - 2774 后缀数组 Height数组模板

求2个字符串最长公共字串。

先合并2个字符串

一定是Height[i],且sa[i],sa[i-1]分别在2个字符串的位置,就一定是公共字串。

为什么呢?

首先我们先合并,(注意:在第一个字符串后面加一个不再范围内的字符,隔开2个字符,这样两个字符串的最长公共字串就转化成了新的字符串后缀的最长公共前缀(满足2个后缀分别在2个字符串原来的位置)。我看很多人没加分隔符,那这组数据就能hack:  a  aa    要么是题意说了第二个字符串更小,要么就是数据水。。。。。。)

任意2个后缀的最长公共前缀不好搞,但是有Height[i]这个神奇的数组。

Height[i]表示:排名i的后缀和排名i-1的后缀的最长公共前缀。那么如果Height[k]且sa[k]在前n个,sa[k-1]在后m个hu或者相反,(即在2个字符串原来的位置),那Height一定是最优解集合。遍历所有Height一定包含最优解。

 

因为我们求得是最长公共字串,既然要最长,他们的字典序排名一定是相邻,可以自己证明下,如果不相邻,一定有相邻的2个分别在2个字符串的后缀,那他们的解肯定更优。(因为所有后缀中开头位置只包含在前n,和后m两中情况)。所以直接搞就行了。

#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstring>
const int MAXN = 3e5 + 10;
using namespace std;
char s[MAXN],s2[MAXN];
int N, M, rak[MAXN], sa[MAXN], tax[MAXN], tp[MAXN],Height[MAXN];
//tp[i]   第二关键字排名i的下标
//rak[i]  下标i的后缀的排名 
//sa[i]   第一关键字排名i的下标。。最终是排名i的后缀的下标
//Height[i]  suffix(sa[i-1]) 和 suffix(sa[i]) 的最长公共前缀长度  
// suffix(i)开头下标为i的后缀 
//我们定义LCP(i,j)为suff(sa[i])与suff(sa[j])的最长公共前缀
//LCP(i,k)=min(LCP(i,j),LCP(j,k)) 对于任意1<=i<=j<=k<=n
//LCP(i,k)=min(LCP(j,j-1)) 对于任意1<i<=j<=k<=n
/*
两个后缀的最大公共前缀
lcp(x,y)=min(heigh[x--y]) 用rmq维护,O(1)查询

可重叠最长重复子串
Height数组里的最大值

不可重叠最长重复子串 POJ1743
首先二分答案xx,对height数组进行分组,保证每一组的min heightminheight都>=x>=x
依次枚举每一组,记录下最大和最小长度,多sa[mx]?sa[mi]>=xsa[mx]?sa[mi]>=x那么可以更新答案

本质不同的子串的数量
枚举每一个后缀,第ii个后缀对答案的贡献为len?sa[i]+1?height[i]
*/ 
void Qsort() {
    for (int i = 0; i <= M; i++) tax[i] = 0;
    for (int i = 1; i <= N; i++) tax[rak[i]]++;
    for (int i = 1; i <= M; i++) tax[i] += tax[i - 1];
    for (int i = N; i >= 1; i--) sa[ tax[rak[tp[i]]]-- ] = tp[i];
    //基数排序,sa[tax[z]--]=tp[i],更新排名
 //z是第二关键字排名i的后缀 放在什么位置。  tax[z]是第一关键字排名一样的放在一起 
}
void SuffixSort() {
    M = 122;
    for (int i = 1; i <= N; i++) rak[i] = s[i] - '0' + 1, tp[i] = i;
    Qsort();
    for (int w = 1, p = 0; p < N; M = p, w <<= 1) {
        p = 0;//这里的p仅仅是一个计数器000
        for (int i = N; i >= N-w+1; i--) tp[++p] = i;
        for (int i = 1; i <= N; i++) if (sa[i] > w) tp[++p] = sa[i] - w; 
 		 Qsort();
        swap(tp, rak);
        rak[sa[1]] = p = 1;
        for (int i = 2; i <= N; i++)
            rak[sa[i]] = (tp[sa[i - 1]] == tp[sa[i]] && tp[sa[i - 1] + w] == tp[sa[i] + w]) ? p : ++p;
    }
    //for (int i = 1; i <= N; i++)
        //printf("%d ", sa[i]);
}
void GetHeight() {
    int j, k = 0;
    for(int i = 1; i <= N; i++) {
        if(k) k--;
        int j = sa[rak[i] - 1];
        while(s[i + k] == s[j + k]) k++;
        Height[rak[i]] = k;
        //printf("%d\n", k);
    }
}
int main() 
{  
    scanf("%s%s", s + 1,s2+1);
    int n = strlen(s + 1);
    int m=strlen(s2+1);
   // if(m>n)m=n;
 	s[n+1]='z'+1;
    for(int i=n+2;i<=n+m+1;i++)
    s[i]=s2[i-n-1];
	N=n+m+1; 
//	cout<<s+1<<endl;
	SuffixSort();
	GetHeight();
//Height[i]  suffix(sa[i-1]) 和 suffix(sa[i]) 的最长公共前缀长度  
	int mx=0;
	for(int i=2;i<=N;i++)
	{
		if(Height[i]>mx)
		{
			if(sa[i]>n&&sa[i-1]<=n)
			mx=Height[i];
			if(sa[i]<=n&&sa[i-1]>n)
			mx=Height[i];
		}
	}
	printf("%d\n",mx);
    return 0;
}

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值