放牧代码和思想
专注自然语言处理、机器学习算法
    This thing called love. Know I would've. Thrown it all away. Wouldn't hesitate.

POJ 3415 Common Substrings 题解《挑战程序设计竞赛》

目录

挑战程序设计竞赛第二版.jpg

POJ 3415 Common Substrings 

公共子串:统计A和B长度不小于K的公共子串个数。

4.7华丽地处理字符串 

后缀数组 

将A和B拼接后,累计分属两者的后缀对应的LCP-K+1即为答案,但穷举不是个好主意。

如果能快速求出任意两个后缀的最长公共前缀的话,利用类似尺取法的技巧就可以在线性时间统计一段区间了。

任意两个后缀的最长公共前缀为该区间的LCP值的最小值。hankcs.com 2017-02-08 下午11.34.17.png

在扫描一段LCA>=K的区域中,如果使用单调栈维护LCP,维护栈顶使LCP最小,就可以快速得到任意爬取区域的最长公共前缀。在“尺取法”爬取的过程中,更新出栈的元素对贡献值的影响,累积到答案中即可。

#include <iostream>
#include <string>
#include <algorithm>

const int MAX_L = 100000 + 1;
const int MAX_N = 2 * MAX_L + 1;
std::string S;
int n, k;
int sa[MAX_N + 1], lcp[MAX_N + 1];  // sa[i] := 字典序为i的后缀的起始下标;lcp[i] := S[sa[i]...]与S[sa[i+1]...]的最长公共前缀长度
int rank[MAX_N + 1], tmp[MAX_N + 1];

// 比较(rank[i], rank[i+k])和(rank[j], rank[j+k])
bool compare_sa(const int i, const int j)
{
    if (rank[i] != rank[j])
    {
        return rank[i] < rank[j];
    }
    return (i + k <= n ? rank[i + k] : -1) < (j + k <= n ? rank[j + k] : -1);
}

// 计算字符串S的后缀数组
void construct_sa()
{
    // 初始长度为1,rank直接取字符的编码
    for (int i = 0; i <= n; i++)
    {
        sa[i] = i;
        rank[i] = i < n ? S[i] : -1;
    }

    // 利用对长度为k的排序的结果对长度为2k的排序
    for (k = 1; k <= n; k <<= 1)
    {
        std::sort(sa, sa + n + 1, compare_sa);

        // 现在tmp中临时储存新计算的rank,再转存回rank中
        tmp[sa[0]] = 0;
        for (int i = 1; i <= n; i++)
        {
            tmp[sa[i]] = tmp[sa[i - 1]] + (compare_sa(sa[i - 1], sa[i]) ? 1 : 0);
        }
        for (int i = 0; i <= n; i++)
        {
            rank[i] = tmp[i];
        }
    }
}

void construct_lcp()
{
    memset(lcp, 0, sizeof(lcp));
    for (int i = 0; i <= n; i++)
    {
        rank[sa[i]] = i;
    }
    int h = 0;
    lcp[0] = 0;
    for (int i = 0; i < n; i++)
    {
        int j = sa[rank[i] - 1];
        if (h > 0)
        {
            h--;
        }
        for (; i + h < n && j + h < n && S[i + h] == S[j + h]; h++);
        lcp[rank[i] - 1] = h;
    }
}

int stack[MAX_N][2];    // 0放lcp,1放个数
long long contribution, top;
using namespace std;

long long solve(int K, int l1, bool is_s1)
{
    long long ans = 0;
    for (int i = 0; i < n; i++)
    {
        if (lcp[i] < K)
        {
            top = contribution = 0;
        }
        else
        {// s1的后缀与s2的后缀的最长公共前缀大于k
            int size = 0;   // 满足条件的s1后缀个数
            if ((is_s1 && sa[i] < l1) || (!is_s1 && sa[i] > l1))
            {
                ++size;
                contribution += lcp[i] - K + 1;
            }
            while (top > 0 && lcp[i] <= stack[top - 1][0])
            {// 单调减栈,栈顶最小
                --top;
                contribution -= stack[top][1] * (stack[top][0] - lcp[i]); // 去掉出栈元素的贡献
                size += stack[top][1];
            }
            if (size)
            {
                stack[top][0] = lcp[i];
                stack[top][1] = size;
                ++top;
            }
            if ((is_s1 && sa[i + 1] > l1) || (!is_s1 && sa[i + 1] < l1)) // 与s2组合
            {
                ans += contribution;
            }
        }
    }
    return ans;
}

int main()
{
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
#endif
    int K;
    while (~scanf("%d", &K), K)
    {
        std::string s1, s2;
        std::cin >> s1 >> s2;
        int l1 = s1.length(), l2 = s2.length();
        S = s1 + '$' + s2;
        n = l1 + l2 + 1;
        construct_sa();
        construct_lcp();
        printf("%lld\n", solve(K, l1, true) + solve(K, l1, false));
    }
#ifndef ONLINE_JUDGE
    fclose(stdin);
#endif
    return 0;
}
16559973 hankcs 3415 Accepted 5072K 3813MS C++ 3092B 2017-02-09 13:48:07

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » POJ 3415 Common Substrings 题解《挑战程序设计竞赛》

评论 欢迎留言

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

我的作品

HanLP自然语言处理包《自然语言处理入门》