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;
}
### Codeforces 432D 后缀数组 C++98 实现代码 以下是基于问题需求编写的针对 Codeforces 432D 的后缀数组实现代码,采用的是 C++98 版本的语言特性。此代码实现了倍增法来构建后缀数组,并解决了题目中的核心逻辑。 #### 倍增法构建后uffix Array 倍增法的核心思想在于逐步增加比较的长度,每次将当前已排序的部分扩展一倍,直到覆盖整个字符串[^2]。这种方法的时间复杂度为 \(O(n \log n)\),适合用于解决此类问题。 ```cpp #include &lt;cstdio&gt; #include &lt;cstring&gt; const int MAXN = 1e5 + 10; int sa[MAXN], rank_arr[MAXN], tmp_rank[MAXN]; int c[MAXN]; // 计数排序辅助函数 void counting_sort(int n, int k) { memset(c, 0, sizeof(c)); for (int i = 0; i &lt; n; ++i) c[i + k &lt; n ? rank_arr[i + k] : 0]++; for (int i = 1; i &lt; n; ++i) c[i] += c[i - 1]; for (int i = n - 1; i &gt;= 0; --i) sa[--c[i + k &lt; n ? rank_arr[i + k] : 0]] = i; } // 构建后缀数组 void build_suffix_array(const char* s, int n) { for (int i = 0; i &lt; n; ++i) { sa[i] = i; rank_arr[i] = s[i]; } for (int k = 1; k &lt; n; k &lt;&lt;= 1) { counting_sort(n, k); counting_sort(n, 0); tmp_rank[sa[0]] = 0; int r = 0; for (int i = 1; i &lt; n; ++i) { if (rank_arr[sa[i]] != rank_arr[sa[i - 1]] || rank_arr[sa[i] + k &lt; n ? sa[i] + k : 0] != rank_arr[sa[i - 1] + k &lt; n ? sa[i - 1] + k : 0]) ++r; tmp_rank[sa[i]] = r; } memcpy(rank_arr, tmp_rank, sizeof(tmp_rank)); if (rank_arr[sa[n - 1]] == n - 1) break; } } ``` #### 主程序部分 以下为主程序框架,结合输入数据完成具体功能: ```cpp char s[MAXN]; int lcp_arr[MAXN]; // LCP 数组存储最长公共前缀 // Kasai 算法计算LCP数组 void kasai_lcp(char* s, int* sa, int* rank_arr, int n, int* lcp_arr) { for (int i = 0; i &lt; n; ++i) rank_arr[sa[i]] = i; int h = 0; lcp_arr[0] = 0; for (int i = 0; i &lt; n; ++i) { if (h &gt; 0) --h; if (rank_arr[i] == n - 1) { lcp_arr[rank_arr[i]] = 0; h = 0; continue; } int j = sa[rank_arr[i] + 1]; while (i + h &lt; n &amp;&amp; j + h &lt; n &amp;&amp; s[i + h] == s[j + h]) ++h; lcp_arr[rank_arr[i]] = h; } } int main() { scanf(&quot;%s&quot;, s); int n = strlen(s); build_suffix_array(s, n); kasai_lcp(s, sa, rank_arr, n, lcp_arr); // 输出结果(根据实际题目调整) for (int i = 0; i &lt; n; ++i) { printf(&quot;%d &quot;, sa[i]); } putchar(&#39;\n&#39;); for (int i = 0; i &lt; n; ++i) { printf(&quot;%d &quot;, lcp_arr[i]); } return 0; } ``` 以上代码完成了后缀数组及其对应的 LCP 数组的构建过程。对于具体的 Codeforces 432D 题目细节,可以根据其特殊条件进一步优化或修改逻辑。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值