CF 319D Have You Ever Heard About The World? 二分Hash判断可行解

题目大意

给你一个字符串 S ,要求你每次找到一个最短的形如XX(即由两个相同的字符串拼接而成)的子串,如有多个找最左边的那一个。然后把这个字符串从 XX 变成 X ,问无法操作后的字符串是什么?

|S|50000

解题思路

要解决这题,有两个关键的性质是一定要发现的。
1. 每次找到的符合要求的子串的程度是不减的。
2. 在删除相同长度的子串时一定是从左到有又删除的。
第二个性质是显然的,题目就是这样规定的。那么第一个性质是为什么呢?我们可以发现,当进行一次操作后(从 XX 变成 X )那么对于这个子串前后长度小于等于|X|的子串是没有影响的,所以第二条性质是成立的。

知道这两个性质后,我们如何判断是否存在一个长度为 2L XX 呢?我们可以每隔 L 设立一个观察点,那么这个长度为2L的子串一定会跨越两个相邻观察点。我们对于两个相邻的观察点求一个最长公共前缀和最长公共后缀,这个可以用二分加 Hash 判断解决。如果这两个长度加起来大于 L ,那么这就是一个可以进行操作的子串。这样做的之间复杂度是O(|S|LlogL) O(|S|L) ,这样似乎一样会超时。但是我们发现,如果我们把长度相同的子串放在一起处理,我们发现不同长度的子串最多只会有 O(|S|) 种。那么每次做之前先判断一下有没有可行解,那么复杂度就变成了 O(|S|1.5+|S|log2|S|)

程序

//CF319D Have You Ever Heard About The World? YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>

using namespace std;
typedef unsigned long long ULL;

const int MAXS = 29, MAXN = 50005;

ULL Sum[MAXN], Fac[MAXN];
int Len, Flag[MAXN];
char S[MAXN];

ULL GetHash(int l, int r) {
    return Sum[r] - Sum[l - 1] * Fac[r - l + 1];
}

int GetLeft(int x, int y) {
    int Ans = 0, l = 1, r = Len - max(x, y) + 1;
    while (l <= r) {
        int Mid = (l + r) >> 1;
        if (GetHash(x, x + Mid - 1) == GetHash(y, y + Mid - 1)) Ans = Mid, l = Mid + 1; else
            r = Mid - 1;
    }
    return Ans;
}

int GetRight(int x, int y) {
    int Ans = 0, l = 1, r = min(x, y);
    while (l <= r) {
        int Mid = (l + r) >> 1;
        if (GetHash(x - Mid + 1, x) == GetHash(y - Mid + 1, y)) Ans = Mid, l = Mid + 1; else
            r = Mid - 1;
    }
    return Ans;
}

bool Check(int len) {
    for (int i = 1; i + len - 1 <= Len; i += len) {
        if (S[i] != S[i + len]) continue;
        if (GetLeft(i, i + len) + GetRight(i, i + len) - 1 >= len) return 1;
    }
    return 0;
}

int main() {
    scanf("%s", S + 1);
    Len = strlen(S + 1);
    Fac[0] = 1;
    for (int i = 1; i <= Len; i ++) {
        Fac[i] = Fac[i - 1] * MAXS;
        Sum[i] = Sum[i - 1] * MAXS + S[i];
    }
    for (int len = 1; len <= Len / 2; len ++) {
        if (!Check(len)) continue;
        for (int i = 1; i <= Len + 1 - 2 * len; i ++) 
            if (Flag[i] != len && GetHash(i, i + len - 1) == GetHash(i + len, i + 2 * len - 1)) 
                for (int j = i; j <= i + len - 1; j ++) Flag[j] = len;
        int New = 0;
        for (int i = 1; i <= Len; i ++) if (Flag[i] != len) S[++ New] = S[i];
        Len = New;
        for (int i = 1; i <= Len; i ++) Sum[i] = Sum[i - 1] * MAXS + S[i];
    }
    for (int i = 1; i <= Len; i ++) printf("%c", S[i]);
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值