CF427D Match & Catch(后缀数组)

题目链接:https://vjudge.net/contest/363453#problem/I
http://codeforces.com/problemset/problem/427/D
在这里插入图片描述在这里插入图片描述
题意:给两个字符串s1和s2,找出s1和s2中的最短公共子串,且这个最短子串只在s1和s2中各出现一次。
解题思路:利用后缀数组的height数组来找后缀的前缀,先将s1和s2串合并在一起,存入r数组,然后将s1与s2合并后的总字符串进行后缀数组求出height数组。
我们可以知道height[i]表示排在在i与i-1的后缀的公共前缀,如果两个后缀串基本相同,那么他们排名一定在临近的位置。
几个关键点:
①如果要满足唯一性,即只出现一次,现在循环到i,首先要满足排名i-1与i的两个后缀串分别在不同的两个串中。
②拿排名i-1,i,i+1为例,如果想要唯一,那么要求height[i]>height[i-1]&&height[i]>height[i+1],因为取前面三者的最小值为min,那么排名为i-2,i-1,i,i+1都有公共前缀部分min,ans=min(ans,max(height[i-1]+height[i+1])+1),因为唯一性,且height[i]>height[i-1]&&height[i]>height[i+1],但i可能比他们多1多2多3多很多,但我们要求最小串,只需要在保证唯一的前提下,找最小串。比如,height[i-1]公共前缀为ab,height[i]公共前缀为abcdef,height[i+1]的公共前缀为abcd,我们要保证最短唯一性,取abcde即可,不需要再去abcdef了,所以ans=min(ans,max(height[i-1]+height[i+1])+1)
③注意两个串的分割,r[len1]=1,r[n]=0,其中n的长度取len1+len2+1

前半部分都是kuangbin的模版

const int MAXN = 20010;
int t1[MAXN], t2[MAXN], c[MAXN];//求 SA 数组需要的中间变量,不需要赋值
 //待排序的字符串放在 s 数组中,从 s[0] 到 s[n-1], 长度为 n, 且最大值小于 m,
 //除 s[n-1] 外的所有 s[i] 都大于 0, r[n-1]=0
 //函数结束以后结果放在 sa 数组中
bool cmp(int* r, int a, int b, int l) {
    return r[a] == r[b] && r[a + l] == r[b + l];
}
void da(int str[], int sa[], int rank[], int height[], int n, int m) {
    n++;
    int i, j, p, * x = t1, * y = t2;
    //第一轮基数排序,如果 s 的最大值很大,可改为快速排序
    for (i = 0; i < m; i++)c[i] = 0;
    for (i = 0; i < n; i++)c[x[i] = str[i]]++;
    for (i = 1; i < m; i++)c[i] += c[i - 1];
    for (i = n - 1; i >= 0; i--)sa[--c[x[i]]] = i;
    for (j = 1; j <= n; j <<= 1) {
        p = 0;
        //直接利用 sa 数组排序第二关键字
        for (i = n - j; i < n; i++)y[p++] = i;//后面的 j 个数第二关键字为空的最小
        for (i = 0; i < n; i++)if (sa[i] >= j)y[p++] = sa[i] - j;
        //这样数组 y 保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for (i = 0; i < m; i++)c[i] = 0;
        for (i = 0; i < n; i++)c[x[y[i]]]++;
        for (i = 1; i < m; i++)c[i] += c[i - 1];
        for (i = n - 1; i >= 0; i--)sa[--c[x[y[i]]]] = y[i];
        //根据 sa 和 x 数组计算新的 x 数组
        swap(x, y);
        p = 1; x[sa[0]] = 0;
        for (i = 1; i < n; i++)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
        if (p >= n)break;
        m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for (i = 0; i <= n; i++)rank[sa[i]] = i;
    for (i = 0; i < n; i++) {
        if (k)k--;
        j = sa[rank[i] - 1];
        while (str[i + k] == str[j + k])k++;
        height[rank[i]] = k;
    }
}
int Rank[MAXN], height[MAXN], sa[MAXN];
char s[MAXN];  //待求字符串s
int r[MAXN];

代码部分:

#include<iostream>
#include<cstdio>
#include<string>
using namespace std;
const int MAXN = 20010;
int t1[MAXN], t2[MAXN], c[MAXN];//求 SA 数组需要的中间变量,不需要赋值
 //待排序的字符串放在 s 数组中,从 s[0] 到 s[n-1], 长度为 n, 且最大值小于 m,
 //除 s[n-1] 外的所有 s[i] 都大于 0, r[n-1]=0
 //函数结束以后结果放在 sa 数组中
bool cmp(int* r, int a, int b, int l) {
    return r[a] == r[b] && r[a + l] == r[b + l];
}
void da(int str[], int sa[], int rank[], int height[], int n, int m) {
    n++;
    int i, j, p, * x = t1, * y = t2;
    //第一轮基数排序,如果 s 的最大值很大,可改为快速排序
    for (i = 0; i < m; i++)c[i] = 0;
    for (i = 0; i < n; i++)c[x[i] = str[i]]++;
    for (i = 1; i < m; i++)c[i] += c[i - 1];
    for (i = n - 1; i >= 0; i--)sa[--c[x[i]]] = i;
    for (j = 1; j <= n; j <<= 1) {
        p = 0;
        //直接利用 sa 数组排序第二关键字
        for (i = n - j; i < n; i++)y[p++] = i;//后面的 j 个数第二关键字为空的最小
        for (i = 0; i < n; i++)if (sa[i] >= j)y[p++] = sa[i] - j;
        //这样数组 y 保存的就是按照第二关键字排序的结果
        //基数排序第一关键字
        for (i = 0; i < m; i++)c[i] = 0;
        for (i = 0; i < n; i++)c[x[y[i]]]++;
        for (i = 1; i < m; i++)c[i] += c[i - 1];
        for (i = n - 1; i >= 0; i--)sa[--c[x[y[i]]]] = y[i];
        //根据 sa 和 x 数组计算新的 x 数组
        swap(x, y);
        p = 1; x[sa[0]] = 0;
        for (i = 1; i < n; i++)
            x[sa[i]] = cmp(y, sa[i - 1], sa[i], j) ? p - 1 : p++;
        if (p >= n)break;
        m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for (i = 0; i <= n; i++)rank[sa[i]] = i;
    for (i = 0; i < n; i++) {
        if (k)k--;
        j = sa[rank[i] - 1];
        while (str[i + k] == str[j + k])k++;
        height[rank[i]] = k;
    }
}
int Rank[MAXN], height[MAXN], sa[MAXN];
char s1[MAXN], s2[MAXN];
int r[MAXN];
int main()
{
	scanf("%s%s",s1,s2);
	int len1=strlen(s1);
	int len2=strlen(s2);
	int n=len1+len2+1;
	int m=0;
	for(int i=0;i<len1;i++)
		r[i]=s1[i];
	r[len1]=1;
	for(int i=0;i<len2;i++)
		r[i+len1+1]=s2[i];
	r[n]=0;
	da(r,sa,Rank,height,n,128);
	height[n+1]=0;
	int ans=n;
	for(int i=2;i<=n;i++)
	{
		int minpos=min(sa[i],sa[i-1]);
		int maxpos=max(sa[i],sa[i-1]);
		if(minpos<len1&&maxpos>len1)
		{
			if(height[i]>height[i+1]&&height[i]>height[i-1])
				ans=min(max(height[i-1],height[i+1])+1,ans);
		}
	}
	ans=(ans==n)?-1:ans;
	cout<<ans<<endl;
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Buyi.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值