Description
String专题又出现啦~(≧▽≦)/~ 这次的题很水的啦0.0
一个字符串的子串是该字符串的一段连续子序列,如bca是abcabc的子串,而cc不是。一个重复块(repeating block)由一个字符串与自身连接而成,如abcabc是一个重复块,而abcabd, ababab不是。
你有一个由拉丁字符组成的字符串。每一步你要找到它的子串中最短的重复块,如果有多于一个,你必须选择最左边的那个。你要将那个形如XX(X - 某个字符串)的重复块替换成X,换句话说你要删除其中的一个X。重复以上步骤直到字符串中不存在重复块。
最终的字符串会是怎样的?看样例解释来更清楚地理解问题描述。
Sample Input
aaaabaaab
Sample Output
ab
Data Constraint
对于10%的数据|S|<=10。
对于30%的数据|S|<=1000。
对于100%的数据1<=|S|<=50000。
Solution
对于这道题我们考虑用hash来判断两个子串是否相同。所以我们先枚举重复串的长度i,显然,若两个长度为i的重复串放在一起,他们肯定会经过(k*i,(k+1) *i),所以我们花O( N/i∗logN )的时间去计算相邻两个(k*i,(k+1) *i)的最长公共前缀和最长公共后缀,若最长公共前缀加最长公共后缀大于当前的i,则说明这里可以构成一个长度为i的重复串,那么就重新从1到n区模拟判断删去重复串,由于字符串缩减得很快,所以总复杂度为O( Nlog2N )。
代码
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define ll long long
using namespace std;
const int maxn=50005,maxn1=10007;
int f[maxn],g[maxn],n,i,t,j,k,l,b[maxn],a[maxn],len,r,mid,bz1[maxn],x,y;
char s[maxn];
bool bz;
int pan(int t,int mid,int z){
if (z) return f[t]-f[t-mid]*g[mid];
return f[t+mid-1]-f[t-1]*g[mid];
}
int before(int t,int k){
int l,r,mid;
if (s[t]!=s[k]) return 0;
l=1;
r=min(t,k-t);
while (l<r){
mid=(l+r+1)/2;
if (pan(t,mid,1)==pan(k,mid,1))l=mid;
else r=mid-1;
}
return l;
}
int after(int t,int k){
int l,r,mid;
if (s[t]!=s[k]) return 0;
l=1;
r=min(k-t,n-k+1);
while (l<r){
mid=(l+r+1)/2;
if (pan(t,mid,0)==pan(k,mid,0))l=mid;
else r=mid-1;
}
return l;
}
int main(){
// freopen("data.in","r",stdin);freopen("data.out","w",stdout);
scanf("%s",s+1);
n=strlen(s+1);g[0]=1;
for (i=1;i<=n;i++){
g[i]=g[i-1]*27;
f[i]=f[i-1]*27+s[i]-96;
}
for (i=1;i<=n;i++){
bz=true;
len=0;
for (j=2;j<=n/i;j++){
t=i*(j-1);k=i*j;
x=before(t,k);
y=after(t,k);
if (x+y>i){
bz=false;break;
}
}
if (bz) continue;
l=1;r=i+1;
while(r+i-1<=n){
x=after(l,r);
if (x>=i){
for (k=l;k<r;k++)
bz1[k]=i;
l+=i;r+=i;
}else l++,r++;
}
for (j=1;j<=n;j++)
if (bz1[j]!=i) s[++len]=s[j];
n=len;
for (j=1;j<=n;j++) f[j]=f[j-1]*27+s[j]-96;
}
for (i=1;i<=n;i++)
printf("%c",s[i]);
printf("\n");
}