ZJNU 1397 - 隐藏口令 (后缀数组)

ZJNU 1397 - 隐藏口令

(加强版)Luogu 1709 - [USACO5.5]隐藏口令Hidden Password

题面

pic


思路

大家都是最小表示法?我不会,我只会后缀数组了哭哭

后缀数组\(sa[i]\)可以表示排名为\(i\)​的后缀起始位置下标

又因为题目要求字符串可循环,为了能够让后缀数组进行处理,将原串扩展一倍后跑后缀数组

其后对于这个长度为\(2n\)​的串,从最高排名开始寻找\(sa[i]\le n\)的位置,这说明这一段表示的后缀在原串中是最小的

如果可以任意输出编号,那么找到的第一个位置的\(sa[i]\)就可以直接输出了,但本题要求字符串相同时需要输出编号最小的

考虑后缀数组的\(height[i]\)​表示排名为\(i\)\(i-1\)的两个后缀的最长公共前缀长度

假如我们找到了第一个满足\(sa[i]\le n\)的位置\(i\),先记录下位置\(i\),然后初始化变量\(pre=n\),表示从\(i\)位置开始向后处理的过程中,连续排名的这些后缀的最长公共前缀长度,所以从\(i\)位置向后寻找时,每次都需要让\(pre=\min(pre,height[i])\)更新最长公共前缀长度

假如我们在\(i\)位置后面又找到了一个新的位置\(j\)满足\(sa[j]\le n\),并且此时\(pre=n\)仍然成立,则说明\(i\)位置开始的口令与\(j\)位置开始的口令是相同的,所以更新答案为\(\min(sa[i],sa[j])\),即最小编号;反之,如果\(pre=n\)​已经不成立了,那么就不需要再更新答案,此前找到的\(i\)已是最优解


#include<bits/stdc++.h>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define all(a) (a).begin(),(a).end()
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> P;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
const ll mod=998244353;
const int dx[8]={0,1,0,-1,1,1,-1,-1},dy[8]={1,0,-1,0,1,-1,1,-1};
void debug(){cerr<<'\n';}template<typename T,typename... Args>void debug(T x,Args... args){cerr<<"[ "<<x<< " ] , ";debug(args...);}
mt19937 mt19937random(std::chrono::system_clock::now().time_since_epoch().count());
ll getRandom(ll l,ll r){return uniform_int_distribution<ll>(l,r)(mt19937random);}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;b>>=1;a=(a+a)%mod;}return r;}
ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}

const int N=200050;
int xx[N],yy[N],cnt[N];
int sa[N],rk[N],height[N];
char str[N];
void getSA_DA(int n,int M){
    int i,j,p,*x=xx,*y=yy;
    for(i=0;i<M;i++)cnt[i]=0;
    for(i=0;i<n;i++)cnt[x[i]=str[i]]++;
    for(i=1;i<M;i++)cnt[i]+=cnt[i-1];
    for(i=n-1;i>=0;i--)sa[--cnt[x[i]]]=i;
    for(j=1,p=1;p<n;j<<=1,M=p){
        for(p=0,i=n-j;i<n;i++)y[p++]=i;
        for(i=0;i<n;i++)if(sa[i]>=j)y[p++]=sa[i]-j;
        for(i=0;i<M;i++)cnt[i]=0;
        for(i=0;i<n;i++)cnt[x[y[i]]]++;
        for(i=1;i<M;i++)cnt[i]+=cnt[i-1];
        for(i=n-1;i>=0;i--)sa[--cnt[x[y[i]]]]=y[i];
        for(swap(x,y),p=1,x[sa[0]]=0,i=1;i<n;i++)
            x[sa[i]]=(y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+j]==y[sa[i]+j])?p-1:p++;
    }
}
void getHeight(int n){
    int i,j,k=0;
    for(i=1;i<=n;i++)rk[sa[i]]=i;
    for(i=0;i<n;height[rk[i++]]=k)
        for(k?k--:0,j=sa[rk[i]-1];str[i+k]==str[j+k];k++);
    for(i=n;i;i--)rk[i]=rk[i-1],sa[i]++;
}

void solve()
{
    int n;
    string s,t;
    cin>>n;
    while(cin>>t)
        s+=t;
    s+=s;
    repp(i,0,n+n)
        str[i]=s[i];
    
    getSA_DA(n+n+1,128);
    getHeight(n+n);
    
    int ans=-1,pre=-1;
    
    rep(i,1,n+n) //此时长度为2n
    {
        pre=min(pre,height[i]); //更新pre
        if(sa[i]<=n) //假如找到合法串
        {
            if(ans==-1) //此前没找到过答案,则该位置是暂时的最优解
            {
                ans=sa[i]-1;
                pre=n; //定此时最长公共前缀为n
            }
            else
            {
                if(pre==n) //最长公共前缀不变,编号取小
                    ans=min(ans,sa[i]-1);
                else if(pre<n) //已经变了的话则直接结束寻找即可
                    break;
            }
        }
    }
    
    cout<<ans<<'\n';
}
int main()
{
    closeSync;
    //multiCase
    {
        solve();
    }
    return 0;
}

posted @ 2021-08-16 20:41  StelaYuri  阅读(48)  评论(0编辑  收藏  举报