【泉州一中国庆集训day6】String

来源:互联网 发布:方伯谦 知乎 编辑:程序博客网 时间:2024/06/02 23:39

题目链接:http://v.qzyz.com/contest/293/problem/3
题目大意:给定一个长度为N的字符串S,有Q组询问,对于每组询问l和r,求字符串S有多少字串T与G=S[l,r]相似。相似的定义:两个字符串的长度len相等,对于任意i<=len,j<=len,满足Ti=Tj且Gi=Gj,或者Ti≠Tj且Gi≠Gj。
数据范围:N,Q<=50 000,字符集为(a,b,c,d,e,f,g,h,i,j)

题解:对于判断字符串相等的问题通常用哈希来求解。在本题中,两个字符串并不要求相等,只需要字母一一对应即可。我们记一个数组a,表示S中与当前位置字符相同的上一个位置的距离,即找到最小的a[i]使得S[i-a[i]]=S[i]。那么两个字符串相似就说明a相等。但是我们注意到,在每个字符第一次出现的位置,a的值不需要相等,我们把这些位置称为特殊点,在两个相似的字符串中,特殊点的位置也应该一一对应。由于字符集大小只有10,所以这样的点最多只有10个。
我们可以给数组a的所有后缀排个序,在比较两个后缀的时候,先找出所有的特殊点,把这些点的位置上的a当成0,然后进行比对。
具体的比较方法是:找出两个串的所有特殊点并排序,然后从前往后比较,直到找到第一个 i 满足两个串的第i+1个特殊点位置不一样或者第i个和第i+1个特殊点之间的串的hash值不一样。然后从第 i 个特殊点的位置起,二分两个串的最长公共前缀长度z,比较两个串的第z+1个位置。如果有一个串的第z+1个位置是特殊点(前面说了当成0),那么该串排前面,否则直接比较两个串的第z+1个位置的a值。如果直到其中一个串结束时两串仍然相等,那么较短的串排前面。
排好顺序后,对于询问 l 和 r ,从以第 l 个位置开始的后缀所在的排名分别往前后二分出左右端点,使得它们的最长公共前缀大于等于r-l+1,即可统计出答案。
时间复杂度O(10nlog^2n(排序)+10nlogn(询问))

(ps:在本题中我是使用了107作为底数和自然溢出来哈希并成功卡过了所有数据,但是建议读者还是多选几个数作为底数或者取模来提高正确率,否则说不定什么时候就被卡了。话说明明上周就想写这篇题解了我是怎么拖到现在的难得我还记得hhhh)

代码如下:

#include <algorithm>#include <cstring>#include <cstdio>using namespace std;const int p=107;int a[50005],b[10],c[50005][10],d[50005],f[50005],g[50005],s[50005],    h[50005],i,n,q,j,x,y;char ch;bool Same(int x,int y,int len){    return f[x+len]-f[x]*g[len]==f[y+len]-f[y]*g[len];}bool check(int x,int y,int len){    if (x+len>n || y+len>n) return 0;    int a1[10],a2[10],i;    for (i=0;i<10;++i)    {        if (s[x]!=i) a1[i]=c[x][i]-x;else a1[i]=len+1;        if (s[y]!=i) a2[i]=c[y][i]-y;else a2[i]=len+1;    }    sort(a1,a1+10);    sort(a2,a2+10);    int z=0;    for (i=0;i<10 && a1[i]==a2[i] && a1[i]<=len+1;++i)        if (z!=a1[i] && !Same(x+z,y+z,a1[i]-1-z)) return 0;            else if ((z=a1[i])>len) return 1;    return 0;}int lcp(int x,int y){    int l=1,r=n-max(x,y),mid,z=0;    for (;l<=r;)    {        mid=(l+r)>>1;        if (Same(x,y,mid)) z=mid,l=mid+1;            else r=mid-1;    }    return z;}bool cmp(int x,int y){    int a1[10],a2[10],i;    for (i=0;i<10;++i)    {        if (s[x]!=i) a1[i]=c[x][i]-x;else a1[i]=n+1-x;        if (s[y]!=i) a2[i]=c[y][i]-y;else a2[i]=n+1-y;    }    sort(a1,a1+10);    sort(a2,a2+10);    int z=0;     for (i=0;i<10 && a1[i]==a2[i];++i)        if (z!=a1[i] && !Same(x+z,y+z,a1[i]-1-z)) break;else z=a1[i];    if (x+z>n || y+z>n) return x+z>n;    z+=lcp(x+z,y+z);    if (x+z>=n || y+z>=n) return x+z>=n;    if (z+1==a1[i] || z+1==a2[i]) return z+1==a1[i];    return a[x+z+1]<a[y+z+1];}int main(){    scanf("%d%d\n",&n,&q);    for (i=1;i<=n;++i)    {        scanf("%c",&ch);        s[i]=ch-'a';    }    scanf("\n");    g[0]=1;f[0]=0;    for (i=1;i<=n;++i)    {        a[i]=i-b[s[i]];        b[s[i]]=i;        f[i]=f[i-1]*p+a[i];        g[i]=g[i-1]*p;    }    for (i=0;i<10;++i) c[n+1][i]=n+1;    for (i=n;i>=0;--i)    {        for (j=0;j<10;++j) c[i][j]=c[i+1][j];        c[i][s[i]]=i;    }    for (i=1;i<=n;++i) d[i]=i;    sort(d+1,d+n+1,cmp);    for (i=1;i<=n;++i) h[d[i]]=i;    for (i=1;i<=q;++i)    {        scanf("%d%d\n",&x,&y);y-=x;x=h[x];        int l=1,r=x-1,mid,ansl=x,ansr=x;        for (;l<=r;)        {            mid=(l+r)>>1;            if (check(d[x],d[mid],y)) ansl=mid,r=mid-1;                else l=mid+1;        }        l=x+1;r=n;        for (;l<=r;)        {            mid=(l+r)>>1;            if (check(d[x],d[mid],y)) ansr=mid,l=mid+1;                else r=mid-1;        }        printf("%d\n",ansr-ansl+1);    }    return 0;}
0 0