CodeForces 427D Match & Catch 后缀数组

题目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;
}

猜你喜欢

转载自blog.csdn.net/Rewriter_huanying/article/details/78707617