【JZOJ4126】【SDOI2015】双旋转字符串(KMP+哈希)

Problem

  给出两个字符串集合 S 和 T,S中的所有字符串长度都为N,T中的所有字符串长度都为M,且N+M为偶数。
  S中有 T o t a l S 个字符串,T中有 T o t a l T 个字符串。每次从 S 和 T 中各选一个串将它们拼接起来,问有多少种方案满足,把这个拼起来的串分成两半后,分成的两个串是循环同构串。

Hint

这里写图片描述

Solution

  设w=n+m,表示某个S和T拼接起来的长度;设 u = w 2 ,表示分成两半后每一半的长度。

N>M

  若N>M,则N>u,即S串会被那条分割线分成一个长度为u的较长的串以及一个长度为n-u的较短的串。还记那个较长的串为S,较短的串为a。
  此时,题目就是要我们求a与某个T拼起来后,与S循环同构的方案数。
  那么,我们可以让a在S上做一遍KMP,看看它能匹配到哪些位置,如图:
这里写图片描述
  S中绿色格子表示被匹到的部分。我们可以发现,S可能会被匹到尾首相连的一部分。所以,我们可以使用惯用套路——把S复制一份再KMP:
这里写图片描述
  我们可以数出图中的u=10,|a|=2,于是n=u+|a|=12,m=u-|a|=8,w=u*2=20。
  那么,可行的串自然是匹配到的绿色部分的前m个字符构成的串(当然,后m个字符也是一样的,随你的便)。前m个字符或后m个字符,任选其一记录,不必重复。比如图中的第一个’aa’起始位置为4(设S的第一个字符的位置为1),那么它在下一半的起始位置即为4+u=14,这两个’aa’相距14-(4+|a|)=u-|a|=m=8,所以恰好一致。
  所以说,我们可以从S的第m+1位开始KMP。毕竟从S[4]往前m位,和从S[14]往前m位是一致的。
  我们可以计算出每个可行串的哈希值,把它累计到一个桶里面。
  当然,如果你暴力去计算哈希值肯定是会TLE的,所以我们可以先计算出S串的哈希值前缀和,然后 O ( 1 ) 计算哈希值。
  那么,有木有可能将同一种可行串算重呢?当然有可能。此时,我们只需设一个标记数组,记录每一种可行串的哈希值在当前这个S串中有没有出现过。当然,这样你每次都要清空这个数组,所以你大可将其开为int数组,直接记录最后一次出现过是在第几个S串中的。
  那么,对于T串的工作就简单了,我们直接计算出它的哈希值,然后累计桶中的cnt即可。

N<M

  形同N>M的做法。只不过我们对于S串,直接计算其哈希值并丢进桶里;对于T串,再将其分割成一个大T串和一个小a串KMP即可。

时间复杂度

  时间复杂度: O ( N T o t a l S + M T o t a l T )

心路历程

  我比赛时就想到了,并且还打了,而且自感有点虚,便打了个双哈希。(双哈希求cnt怎么搞?取最小)然而还是惨淡地:
这里写图片描述
  我发现有许多个点我的答案恰好是它的100倍,而且N=M=100。我又发现我的代码(伪代码)是这样打的:

    for(;Ts;Ts--)
    {
        scanf("%s",s+1);
        if(n<m){直接计算;continue;}
        KMP;
    }

    for(;Tt;Tt--)
    {
        scanf("%s",s+1);
        if(n>m){直接求答案;continue;}
        KMP;
    }

  这样当n=m的时候,两者都会进行KMP。
  当然,我本来就打算让其中一个进行KMP,这样的话将S复制一遍变成SS,a则为空串,所以每个位置都会被匹到,所以每m个字符均为可行串。
  但是它把T也进行KMP了,那样T的每个地方都会被统计答案(也即我将S循环,却又将T循环,然后来累计答案)。于是答案就恰好大了M=100倍。
  然后我略作修改,又满怀壮志地提交了,结果又:
这里写图片描述
  然后我发现我调试时将数组开小了,忘记开回去了。。。
  就此被坑掉100points。。。o(╥﹏╥)o
这里写图片描述

Code

#include <cstdio>
#include <cstring>
#include <algorithm>
#define fi first
#define se second
#define mp make_pair
#define min(a,b) ((a)<(b)?(a):(b))
#define fo(i,a,b) for(i=a;i<=b;i++)
using namespace std;
typedef long long ll;
typedef pair<ll,ll> P;

const int N=4e6+1;
const ll X=13331,M1=999983,M2=1e6+3;
int i,j,Ts,Tt,n,m,w,u,v,b1[M1],b2[M2],h1[M1],h2[M2],len,next[N];
char s[N],a[N];
ll x1,x2,ans;
P cm[N],sum[N];

void pre()
{
    cm[0]=mp(1,1);
    fo(i,1,w)cm[i]=mp(cm[i-1].fi*X%M1,cm[i-1].se*X%M2);
    memset(b1,127,sizeof b1);
    memset(b2,127,sizeof b2);
}

void init(int n)
{
    int i;
    fo(i,1,n)
    {
        ll x=s[i]-'a';
        sum[i]=mp((sum[i-1].fi*X+x)%M1,(sum[i-1].se*X+x)%M2);
    }
}

void gethash(int l,int r)
{
    x1=(sum[r].fi-sum[l-1].fi*cm[r-l+1].fi%M1+M1)%M1;
    x2=(sum[r].se-sum[l-1].se*cm[r-l+1].se%M2+M2)%M2;
}

void KMP(bool t)
{
    int j,k=0;
    fo(j,2,v)
    {
        while(k&&a[j]!=a[k+1])k=next[k];
        if(a[j]==a[k+1])next[j]=++k;
    }

    k=0;
    fo(j,len+1,w)
    {
        while(k&&s[j]!=a[k+1])k=next[k];
        if(k<v&&s[j]==a[k+1])k++;
        if(k==v)
        {
            int st=j-v+1;
            gethash(st-len,st-1);
            if(b1[x1]>Ts+Tt||b2[x2]>Ts+Tt)
            {
                b1[x1]=b2[x2]=Ts+Tt;
                if(!t)
                        h1[x1]++,h2[x2]++;
                else    ans+=1ll*min(h1[x1],h2[x2]);
            }
            k=next[k];
        }
    }
}

int main()
{
    scanf("%d%d%d%d",&Ts,&Tt,&n,&m);
    u=(w=n+m)>>1;pre();

    for(;Ts;Ts--)
    {
        scanf("%s",s+1);

        if(n<=m)
        {
            init(n);
            h1[sum[n].fi]++;
            h2[sum[n].se]++;
            continue;
        }

        v=0;
        fo(j,u+1,w)
        {
            if(j<=n)a[++v]=s[j];
            s[j]=s[j-u];
        }
        len=m;
        init(w);
        KMP(0);
    }

    for(;Tt;Tt--)
    {
        scanf("%s",s+1);

        if(n>m)
        {
            init(m);
            ans+=1ll*min(h1[sum[m].fi],h2[sum[m].se]);
            continue;
        }

        v=0;
        fo(j,1,m-u)a[++v]=s[j];
        fo(j,1,u)s[j]=s[j+m-u];
        fo(j,j,w)s[j]=s[j-u];
        len=n;
        init(w);
        KMP(1);
    }

    printf("%lld",ans);
}

猜你喜欢

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