题目大意
给你一个字符串
S
,要求你每次找到一个最短的形如
解题思路
要解决这题,有两个关键的性质是一定要发现的。
1. 每次找到的符合要求的子串的程度是不减的。
2. 在删除相同长度的子串时一定是从左到有又删除的。
第二个性质是显然的,题目就是这样规定的。那么第一个性质是为什么呢?我们可以发现,当进行一次操作后(从
XX
变成
X
)那么对于这个子串前后长度小于等于
知道这两个性质后,我们如何判断是否存在一个长度为
2L
的
XX
呢?我们可以每隔
L
设立一个观察点,那么这个长度为
程序
//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]);
}