ZJNU 1397 - 隱藏口令 (字尾陣列)
阿新 • • 發佈:2021-08-16
(加強版)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\)
#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; }