[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\times10^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("\n");
    }
    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("\n");
    }
    return 0;
}
/*
6
a
c
a
b
c
b
*/
posted @ 2020-05-11 10:11  Valk3  阅读(183)  评论(0编辑  收藏  举报