CF319D Have You Ever Heard About the Word?

发布于 2020-07-21  2.62k 次阅读


CF319D

这么棒的题居然没有人写。

很类似的题:P1117(也是黑色的)(和我写的这道题的的博客

题意分析

题目意思是告诉我们一个字符串,然后让我们从短到长依次缩短相邻且相同的字符串(例如abcabc变为abc)。

我们枚举缩短的字符串的长度$len$,然后判断有没有长度为$2 \times len$的相邻且相同的字符串。一个常规的方法是我们在字符串上每隔$len$长度放置一个观察点,对于两个观察点,对相邻两个观察点做最长公共前缀(LCP)和最长公共后缀(LCS),显然将前缀和后缀加起来而组成的字符串即为在两点上的相同串。且若相邻两串的$LCP+LCS>=len$,则显然他们是相连的(虽然$LCP+LCS>len$的情况中字符串长度长过len,但是出于贪心考虑,我们会将LCS中多余的部分切去)。这是删去其中的左侧串就可以了(因为右侧串可能会继续匹配)。当然删除之后我们需要重新构造字符串和哈希串。

对于求LCP和LCS的部分,我们可以使用字符串哈希+二分的方法,二分LCP或LCS的长度并且判断是否相同。

代码

#include <bits/stdc++.h>
using namespace std;

const int mod = 1e9 + 7;

long long a[50010], w[50010], p[50010];
char s[50010];

int get(int l, int r)
{ //计算字符串中l~r段的哈希值
    return ((w[r] - w[l - 1] * p[r - l + 1]) % mod + mod) % mod;
}

int n, now;

int lcp(int x, int y) //求最长前缀
{
    int l = 0, r = now;
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (get(x - mid + 1, x) == get(y - mid + 1, y))
            l = mid;
        else
            r = mid - 1;
    }
    return l;
}

int lcs(int x, int y) //求最长后缀
{
    int l = 0, r = n - y + 1;
    while (l < r)
    {
        int mid = l + r + 1 >> 1;
        if (get(x, x + mid - 1) == get(y, y + mid - 1))
            l = mid;
        else
            r = mid - 1;
    }
    return l;
}

int main()
{
    scanf("%s", s + 1);
    n = strlen(s + 1);
    p[0] = 1;
    for (int i = 1; i <= n; i++)
    { //字符串哈希
        p[i] = p[i - 1] * 31 % mod;
        w[i] = (w[i - 1] * 31 + s[i] - 'a') % mod;
    }
    for (int len = 1; len <= n; len++)
    { //从小到大枚举删除区间的长度
        bool flag = false;
        int j = len, k = len << 1; //两个观察点
        now = len;
        while (k <= n)
        {
            int LCP = lcp(j, k), LCS = lcs(j, k);
            if (LCP + LCS > len)
            {
                for (int i = j - LCP + 1; i <= j - LCP + len; i++)
                    a[i] = len; //懒惰标记删除
                now = LCP;      //一个细节:避免下一次的LCS与这次的LCP重合
                flag = true;
            }
            else
                now = len; //不会重合,则重置
            j += len;      //转移到下一个观察点
            k += len;
        }
        if (flag)
        {
            int cnt = 0;
            for (int i = 1; i <= n; i++)
                if (a[i] != len)
                    s[++cnt] = s[i]; //重构字符串
            n = cnt;
            for (int i = 1; i <= n; i++)
                w[i] = (w[i - 1] * 31 + s[i] - 'a') % mod; //重构哈希数组
        }
    }
    for (int i = 1; i <= n; i++)
        putchar(s[i]);
    return 0;
}

我缓慢吐出一串啊吧啊吧并不再想说话