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

3 篇文章 0 订阅
2 篇文章 0 订阅

题目链接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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值