1. 程式人生 > 其它 >ZJNU 1397 - 隱藏口令 (字尾陣列)

ZJNU 1397 - 隱藏口令 (字尾陣列)

ZJNU 1397 - 隱藏口令

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

題面


思路

大家都是最小表示法?我不會,我只會後綴陣列了哭哭

字尾陣列\(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;
}