【JZOJ4022】【CF319D】Have You Ever Heard About the Word?(哈希)

Problem

  有一个字符串S(|S| ≤ 50000),每次你需要找到最短的形如XX(可以在中间被分成相同的两份)的子串,有多个最短的则选取最左边的,并删除其中的一份(XX → X)。输出最终字符串。

Solution

  这道题我一看,感觉可能是SAM之类的东西,想了半天也没想出个什么来,于是就打了个 O ( n 3 ) 的暴力。正解是哈希。

RMQ:65points

  既然要求区间的值,那么很(不可能)容易想到RMQ。所以首先,我们先用 O ( n l o g 2 n ) 的时间预处理出每个位置开始,从左边走和从右边走2的次幂步构成的区间的哈希值。由于我担心它太长了,用单哈希可能会判断错误,所以我用了双哈希。
  预处理完后,我们发现它要求从短的重复块开始替换,所以我们可以先枚举一个重复块半径(即一个X的长度)len。然后,我们使用一个很鲜为人知(其实是我不知道)的套路,就是在原串中,每隔len步设一个观察点。那么,一个长度为2*len的重复块定然会跨越且只会跨越两个观察点。
  所以,我们可枚举一个i观察点,然后花费 O ( l o g 2 n ) 的时间(不必二分,可参考倍增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,得花费 O ( n l o g 2 n ) 的时间,这显然不值。所以,我们可以先给要删的部分打标记,然后你之后求出的其他重复块不要占了有标记的地方。求完当前的len的所有要删的部分后,我们可以一起删掉它们,用两个指针即可。
  这样的话,最坏情况则是它每种len只删一块,但是我们对于每种len,只要有删,就得重构。但它只能如下地删: 1 + 2 + 3 + . . . + 2 n ,所以,如果按照最坏情况,它只会有 n 种不同的len。
  当然,求最长公共前、后缀也需要时间。求这个的最坏情况是每种len都不删,那么总共有 n 2 种不同的len。但是对于总的观察点数,其实为 i = 1 n 2 n i 。我们知道, i = 1 n n i 都只有 n l o g 2 n 虽然我不会证明),这个肯定小于 n l o g 2 n
  时间复杂度: O ( n l o g 2 n ( n + l o g 2 n ) )

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]*某个数。这样一来,我们每次重构就可以做到 O ( n ) 的复杂度。
  时间复杂度: O ( n ( n + ( l o g 2 n ) 2 ) )

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');
}

猜你喜欢

转载自blog.csdn.net/qq_36551189/article/details/79717658
今日推荐