zoukankan      html  css  js  c++  java
  • [USACO07DEC]Best Cow Line G 字符串hash || 后缀数组

    [USACO07DEC]Best Cow Line G

    [USACO07DEC]Best Cow Line G


    小声哔哔:字符串hash牛逼

    题意

    给出一个字符串,每次可以从字符串的首尾取出一个字符,放到队列的尾部,求可以得到的最小的字典序是多少?

    思路1

    此时字符串首尾的下标分别为l,r。

    如果str[l]!=str[r]:取较小的字符串

    如果str[l]==str[r]:找到第一个非负整数x,使得str[l+x]!=str[r-x]

    ​ 如果str[l+x]<str[r-x],那么此时取str[l],否则取str[r]

    数据范围是(1 leq N leq 5 imes10^5),如果暴力复杂度比较高

    如果快速找到x是关键,我想了字符串hash,没想到二分判断条件。(我好菜啊啊啊啊啊啊啊)
    对于l,r,我们二分x的值,找到第一个hash值不想等的x。

    代码

    #include<bits/stdc++.h>
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int N=1e6+10;
    const int mod=1e9+7;
    const int inf=0x3f3f3f3f;
    
    char str[N];
    string ans;
    ull bin[N],hash1[N],hash2[N];
    int n;
    void init()
    {
        bin[0]=1;
        for(int i=1; i<=n; i++)
        {
            bin[i]=bin[i-1]*137;
            hash1[i]=hash1[i-1]*137+str[i]-'a'+1;
            hash2[i]=hash2[i-1]*137+str[n-i+1]-'a'+1;
        }
    }
    ull get1(int l,int r)
    {
        return hash1[r]-hash1[l-1]*bin[r-l+1];
    }
    ull get2(int l,int r)
    {
        return hash2[r]-hash2[l-1]*bin[r-l+1];
    }
    int solve(int aga,int en)
    {
        int l=0,r=(en-aga)/2,ans=0;
        while(l<=r)
        {
            int mid=(l+r)/2;
            if(get1(aga,aga+mid)!=get2(n+1-en,n+1-en+mid))
            {
                ans=mid;
                r=mid-1;
            }
            else
                l=mid+1;
        }
        return ans;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1; i<=n; i++)
        {
            getchar();
            scanf("%c",&str[i]);
        }
        init();
        int l=1,r=n;
        while(l<=r)
        {
            if(l==r)
            {
                ans+=str[l];
                break;
            }
            if(str[l]<str[r])
                ans+=str[l++];
            else if(str[l]>str[r])
                ans+=str[r--];
            else
            {
                int len=solve(l,r);
                if(str[l+len]<str[r-len])
                    ans+=str[l++];
                else
                    ans+=str[r--];
            }
        }
        for(int i=0; i<ans.size(); i++)
        {
            printf("%c",ans[i]);
            if((i+1)%80==0)
                printf("
    ");
        }
        return 0;
    }
    

    思路2

    pre[i]表示以i开头的前缀(即把以i结尾的前缀倒过来)

    suf[i]表示以i开头的后缀

    str[l]==str[r]的时候,我们只需要比较pre[r]suf[l]的排名

    对于pre[r],我们在结尾加一个字符,然后把原串反过来添加到末尾,求后缀数组,pre[r]就是suf[n+n-r+2]

    比如acabca,处理完就是acabca#acbaca

    比较两个c的时候,其实就是比较第一个c的排名和倒数第二个c的排名

    代码

    #include <bits/stdc++.h>
    #define pb push_back
    using namespace std;
    typedef long long ll;
    typedef unsigned long long ull;
    const int N = 1e6 + 10;
    const int mod = 1e9 + 7;
    const int inf = 0x3f3f3f3f;
    
    char str[N];
    string ans;
    int n, m, sa[N], rk[N], oldrk[N<<1], pos[N], cnt[N];
    bool cmp(int x, int y, int k)
    {
        return oldrk[x] == oldrk[y] && oldrk[x + k] == oldrk[y + k];
    }
    void getsa()
    {
        m = 122;
        for (int i = 1; i <= n; i++)
            ++cnt[rk[i] = str[i]];
        for (int i = 1; i <= m; i++)
            cnt[i] += cnt[i - 1];
        for (int i = n; i; i--)
            sa[cnt[rk[i]]--] = i;
        for (int k = 1; k <= n; k <<= 1)
        {
            int num = 0;
            for (int i = n - k + 1; i <= n; i++)
                pos[++num] = i;
            for (int i = 1; i <= n; i++)
            {
                if (sa[i] > k)
                    pos[++num] = sa[i] - k;
            }
            memset(cnt,0,sizeof(cnt));
            for(int i=1;i<=n;i++)
                ++cnt[rk[i]];
            for(int i=1;i<=m;i++)
                cnt[i]+=cnt[i-1];
            for (int i = n; i; i--)
                sa[cnt[rk[pos[i]]]--] = pos[i];
            num = 0;
            memcpy(oldrk, rk, sizeof(rk));
            for (int i = 1; i <= n; i++)
                rk[sa[i]]=cmp(sa[i],sa[i-1],k)?num:++num;
            if(num==n) break;
            m=num;
        }
        for(int i=1;i<=n;i++)
            rk[sa[i]]=i;
    }
    int main()
    {
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            getchar();
            scanf("%c",&str[i]);
        }
        str[n+1]='0';
        for(int i=n+2;i<=n*2+1;i++)
            str[i]=str[2*n-i+2];
        n=n*2+1;
        getsa();
        int l=1,r=n/2;
        while(l<=r)
        {
            if(str[l]<str[r]) ans+=str[l++];
            else if(str[l]>str[r]) ans+=str[r--];
            else
            {
                if(rk[l]<rk[n/2+n/2-r+2]) ans+=str[l++];
                else ans+=str[r--];
            }
        }
        for(int i=0;i<ans.size();i++)
        {
            printf("%c",ans[i]);
            if((i+1)%80==0) printf("
    ");
        }
        return 0;
    }
    /*
    6
    a
    c
    a
    b
    c
    b
    */
    
  • 相关阅读:
    BZOJ 1500 维修数列
    BZOJ 1501 智慧珠游戏
    BZOJ 1507 Editor
    BZOJ 3223 文艺平衡树
    BZOJ 3224 普通平衡树
    BZOJ 3196 二逼平衡树
    BZOJ 1048 分割矩阵
    BZOJ 1047 理想的正方形
    BZOJ 1046 上升序列
    BZOJ 1045 糖果传递
  • 原文地址:https://www.cnblogs.com/valk3/p/12867398.html
Copyright © 2011-2022 走看看