CF319D
这么棒的题居然没有人写。
很类似的题:P1117(也是黑色的)(和我写的这道题的的博客)
题意分析
题目意思是告诉我们一个字符串,然后让我们从短到长依次缩短相邻且相同的字符串(例如abcabc
变为abc
)。
我们枚举缩短的字符串的长度\(len\),然后判断有没有长度为\(2 * 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;
}