Problem
有一个字符串S(|S| ≤ 50000),每次你需要找到最短的形如XX(可以在中间被分成相同的两份)的子串,有多个最短的则选取最左边的,并删除其中的一份(XX → X)。输出最终字符串。
Solution
这道题我一看,感觉可能是SAM之类的东西,想了半天也没想出个什么来,于是就打了个 的暴力。正解是哈希。
RMQ:65points
既然要求区间的值,那么很(不可能)容易想到RMQ。所以首先,我们先用
的时间预处理出每个位置开始,从左边走和从右边走2的次幂步构成的区间的哈希值。由于我担心它太长了,用单哈希可能会判断错误,所以我用了双哈希。
预处理完后,我们发现它要求从短的重复块开始替换,所以我们可以先枚举一个重复块半径(即一个X的长度)len。然后,我们使用一个很鲜为人知(其实是我不知道)的套路,就是在原串中,每隔len步设一个观察点。那么,一个长度为2*len的重复块定然会跨越且只会跨越两个观察点。
所以,我们可枚举一个i观察点,然后花费
的时间(不必二分,可参考倍增LCA的那种方法,按二进制从高到低逐位枚举)求第i个观察点和第i+1个观察点的最长公共前、后缀,分别设其为cp、cs,若cs+cp>len(注意,是>,不是≥,因为你计算前、后缀肯定会算重一个点,所以其实要满足cs+cp-1≥len,也即cs+cp>len),则说明此处有一个长度为2*len的重复块。由于要优先做靠左边的块,所以设第i个观察点的位置为b1,第i+1个观察点的位置为b2,则被做的块为[b1-cs+1,b1-cs+2*len]。我们考虑删该块的左半部分,因为其右半部分可能与之后的串还能形成其他的块。
但是,我们发现,删完左半部分之后,我们还需要重构原串,还需要重新预处理RMQ,得花费
的时间,这显然不值。所以,我们可以先给要删的部分打标记,然后你之后求出的其他重复块不要占了有标记的地方。求完当前的len的所有要删的部分后,我们可以一起删掉它们,用两个指针即可。
这样的话,最坏情况则是它每种len只删一块,但是我们对于每种len,只要有删,就得重构。但它只能如下地删:
,所以,如果按照最坏情况,它只会有
种不同的len。
当然,求最长公共前、后缀也需要时间。求这个的最坏情况是每种len都不删,那么总共有
种不同的len。但是对于总的观察点数,其实为
。我们知道,
都只有
(虽然我不会证明),这个肯定小于
。
时间复杂度:
。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 500010
#define X 13331
#define M1 1000003
#define M2 1000000007
#define ll long long
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
int i,j,n,len,b1,b2,cs,cp,la,st,en,cnt;
char s[N];
ll p1[N][16],p2[N][16],s1[N][16],s2[N][16];
bool p[N];
void init()
{
int i,j,x;
ll c1,c2;
fd(i,n,1)
{
p1[i][0]=p2[i][0]=s[i];
x=1;c1=c2=X;
fo(j,1,15)
{
if(i+x*2-1>n)break;
p1[i][j]=(p1[i][j-1]*c1+p1[i+x][j-1])%M1;
p2[i][j]=(p2[i][j-1]*c2+p2[i+x][j-1])%M2;
x<<=1;
c1=c1*c1%M1;
c2=c2*c2%M2;
}
}
fo(i,1,n)
{
s1[i][0]=s2[i][0]=s[i];
x=1;c1=c2=X;
fo(j,1,15)
{
if(i-x*2+1<1)break;
s1[i][j]=(s1[i-x][j-1]*c1+s1[i][j-1])%M1;
s2[i][j]=(s2[i-x][j-1]*c2+s2[i][j-1])%M2;
x<<=1;
c1=c1*c1%M1;
c2=c2*c2%M2;
}
}
}
int prefix(int a,int b)
{
int i,x=1<<15,ans=0;
fd(i,15,0)
{
if(b+x-1<=n&&p1[a][i]==p1[b][i]&&p2[a][i]==p2[b][i])
{
ans+=x;
a+=x;
b+=x;
}
x>>=1;
}
return ans;
}
int suffix(int a,int b)
{
int i,x=1<<15,ans=0;
fd(i,15,0)
{
if(a-x+1>0&&s1[a][i]==s1[b][i]&&s2[a][i]==s2[b][i])
{
ans+=x;
a-=x;
b-=x;
}
x>>=1;
}
return ans;
}
int main()
{
s[0]=' ';
scanf("%s",&s[1]);
n=strlen(s)-1;
fo(i,1,n)s[i]-='a';
init();
fo(len,1,n/2)
{
cnt=la=0;
fo(i,1,n/len-1)
{
b1=i*len;b2=b1+len;
cs=min(suffix(b1,b2),len-la);
cp=prefix(b1,b2);
la=0;
if(cs+cp>len)
{
st=b1-cs+1;en=st+len-1;
fo(j,st,en)p[j]=1;
cnt+=len;
la=en-b1;
}
}
if(cnt)
{
j=0;
fo(i,1,n)
{
if(j<=n)
do
j++;
while(p[j]&&j<=n);
s[i]=s[j];
}
n-=cnt;
init();
fo(i,1,n)p[i]=0;
}
}
fo(i,1,n)putchar(s[i]+'a');
}
前缀和:100points
事实上,我们不需要用RMQ。我们对于一段字符串,可以直接求一个类似前缀和的哈希值。这样子的话,对于[l,r]这段区间的值,即为sum[r]-sum[l]*某个数。这样一来,我们每次重构就可以做到
的复杂度。
时间复杂度:
。
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define N 500010
#define X 13331
#define ll long long
#define ull unsigned ll
#define fo(i,a,b) for(i=a;i<=b;i++)
#define fd(i,a,b) for(i=a;i>=b;i--)
const ull M[2]={1000003,1000000007};
int i,j,n,len,b1,b2,cs,cp,la,st,en,cnt;
char s[N];
ull cj[2][N],sum[2][N];
bool p[N];
void init()
{
int i,j;
fo(i,1,n)
fo(j,0,1)
sum[j][i]=(sum[j][i-1]*X+s[i])%M[j];
}
inline ull calc(int t,int a,int b)
{
return (sum[t][b]-sum[t][a-1]*cj[t][b-a+1]%M[t]+M[t])%M[t];
}
inline bool cmp(int a,int b,int x,int y)
{
return calc(0,a,b)==calc(0,x,y)&&calc(1,a,b)==calc(1,x,y);
}
int prefix(int a,int b)
{
int l=0,r=n-b+1,mid;
while(l<r)
{
mid=(l+r+1)/2;
if(cmp(a,a+mid-1,b,b+mid-1))
l=mid;
else r=mid-1;
}
return l;
}
int suffix(int a,int b)
{
int l=0,r=a,mid;
while(l<r)
{
mid=(l+r+1)/2;
if(cmp(a-mid+1,a,b-mid+1,b))
l=mid;
else r=mid-1;
}
return l;
}
int main()
{
s[0]=' ';
scanf("%s",&s[1]);
n=strlen(s)-1;
fo(i,1,n)s[i]-='a';
cj[0][0]=cj[1][0]=1;
fo(i,1,n)
fo(j,0,1)
cj[j][i]=cj[j][i-1]*X%M[j];
init();
fo(len,1,n/2)
{
cnt=la=0;
fo(i,1,n/len-1)
{
b1=i*len;b2=b1+len;
cs=min(suffix(b1,b2),len-la);
cp=prefix(b1,b2);
la=0;
if(cs+cp>len)
{
st=b1-cs+1;en=st+len-1;
fo(j,st,en)p[j]=1;
cnt+=len;
la=en-b1;
}
}
if(cnt)
{
j=0;
fo(i,1,n)
{
if(j<=n)
do
j++;
while(p[j]&&j<=n);
s[i]=s[j];
}
n-=cnt;
init();
fo(i,1,n)p[i]=0;
}
}
fo(i,1,n)putchar(s[i]+'a');
}