CodeForces 427D Match & Catch 后缀数组

来源:互联网 发布:淘宝培训机构靠谱吗 编辑:程序博客网 时间:2024/05/17 04:13

题目:https://cn.vjudge.net/problem/CodeForces-427D

题意:给出两个字符串,求最小公共子串长度,使这个子串仅在两个串均出现一次。

思路:将这两个字符串拼成一个串,中间用一个没出现过的字符隔开(我用的空格),对合成的串求后缀数组,这样两个数组的公共子串所在的后缀在sa中应该是相邻的。通过观察,只有当这两个sa中相邻的后缀sa[i]和sa[i-1]左右的后缀都不含这个子串时,才能保证子串在两个串都只出现一次。

具体来说就是
(1)后缀sa[i]和sa[i-1]分别属于两个字符串
(2)height[i-1] < height[i] && height[i+1] < height[i]
(3)这个最小公共子串长度就是max(height[i-1], height[i+1]) + 1

整体扫过一遍height数组统计最小值即可,没有满足的情况时输出-1

代码:c++

#include <cstdio>#include <iostream>#include <cstring>#include <string>#include <algorithm>#include <cmath>using namespace std;const int maxn = 20000;const int INF = 2147483647;char s[maxn];int sa[maxn], t[maxn], t2[maxn], c[maxn];int n;//字符串s的下标从1到n,且s[0]必须为空字符//构造字符串s的后缀数组。每个字符ASCII值必须在0~m-1范围内void build_sa(int m, int n){    n++;    int *x = t;    int *y = t2;    //基数排序    for (int i = 0; i < m; i++)    {        c[i] = 0;    }    for (int i = 0; i < n; i++)    {        x[i] = s[i];        c[x[i]]++;    }    for (int i = 1; i < m; i++)    {        c[i] += c[i - 1];    }    for (int i = n - 1; i >= 0; i--)    {        sa[--c[x[i]]] = i;    }    for (int k = 1; k <= n; k <<= 1)    {        int p = 0;        //直接利用sa排序第二关键字        for (int i = n - k; i < n; i++)        {            y[p++] = i;        }        for (int i = 0; i < n; i++)        {            if (sa[i] >= k)            {                y[p++] = sa[i] - k;            }        }        //基数排序第一关键字        for (int i = 0; i < m; i++)        {            c[i] = 0;        }        for (int i = 0; i < n; i++)        {            c[x[y[i]]]++;        }        for (int i = 0; i < m; i++)        {            c[i] += c[i - 1];        }        for (int i = n - 1; i >= 0; i--)        {            sa[--c[x[y[i]]]] = y[i];        }        //根据sa和y数组计算新的x数组        swap(x, y);        p = 1;        x[sa[0]] = 0;        for (int i = 1; i < n; i++)        {            if (y[sa[i - 1]] == y[sa[i]] && y[sa[i - 1] + k] == y[sa[i] + k])            {                x[sa[i]] = p - 1;            }            else            {                x[sa[i]] = p++;            }        }        //以后即使继续倍增,sa也不会改变        if (p >= n)        {            break;        }        m = p;//下次基数排序的最大值    }}int ranks[maxn], height[maxn];//ranks[i]是后缀i的排名//height[i]是后缀i和后缀i-1的最长公共前缀长度void getHeight(int n){    n++;    int k = 0;    for (int i = 0; i < n; i++)    {        ranks[sa[i]] = i;    }    for (int i = 0; i < n; i++) {        if (ranks[i] == 0)        {            height[0] = 0;            continue;        } // 第一个后缀的 LCP 为 0。        if (k)        {            k--; // 从 k - 1 开始推        }        int j = sa[ranks[i] - 1];        while (s[i + k] == s[j + k] && i + k < n && j + k < n)        {            k++;        }        height[ranks[i]] = k;    }}int belong[maxn];//0 - 空字符,1 - 第一个串,2 - 第二个串void init(){    scanf("%s", s + 1);    n = strlen(s + 1);    for (int i = 1; i <= n; i++)    {        belong[i] = 1;    }    s[++n] = ' ';    scanf("%s", &s[n + 1]);    int st2 = n + 1;    n += strlen(&s[n + 1]);    for (int i = st2; i <= n; i++)    {        belong[i] = 2;    }    build_sa(200, n);    getHeight(n);}int solve(){    int minn = INF;    for (int i = 1; i <= n; i++)    {        if (belong[sa[i - 1]] + belong[sa[i]] == 3)        {            if (height[i - 1] < height[i] && height[i + 1] < height[i])            {                minn = min(minn, max(height[i - 1], height[i + 1]) + 1);            }        }    }    return minn == INF ? -1 : minn;}int main(){    init();    printf("%d\n", solve());    return 0;}