1. 程式人生 > >洛谷3975 BZOJ3998 TJOI2015 弦論 字尾自動機

洛谷3975 BZOJ3998 TJOI2015 弦論 字尾自動機

題目連結

題意:
給你一個字串,問你在算重複/不算重複的情況下第k大的字串是什麼,不存在輸出-1。n<=5e5。

題解:
如果不算重複的話和這個題一樣。看連結裡的題解就行,我不再重複了。

那麼我們就講一下算重複的做法。算重複其實就是要通過right來計算相同的情況,計算的方法和洛谷模板SAM的做法類似。都是在parent樹上dfs,然後用子節點size更新父節點,其他的與可不算重複的一樣做了。

程式碼:

#include <bits/stdc++.h>
using namespace std;

int opt,n,k,fa[2000010],cnt=1,
rt=1,lst=1,ch[2000010][26],len[2000010]; int sz[2000010],ri[2000010],a[2000010],rk[2000010]; char s[500010]; inline void insert(int x) { int cur=++cnt,pre=lst; ri[cur]=1; lst=cur; len[cur]=len[pre]+1; for(;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=cur; if(!pre) fa[cur]=rt; else { int ji=ch[pre][x]; if(len[ji]==len[
pre]+1) fa[cur]=ji; else { int gg=++cnt; memcpy(ch[gg],ch[ji],sizeof(ch[ji])); len[gg]=len[pre]+1; fa[gg]=fa[ji]; fa[ji]=fa[cur]=gg; for(;pre&&ch[pre][x]==ji;pre=fa[pre]) ch[pre][x]=gg; } } } inline void query(int k) { if(sz[rt]<k) { printf("-1\n"); return
; } int ji=rt; while(k) { if(ji!=rt) k-=ri[ji]; if(k<=0) break; for(int i=0;i<=25;++i) { if(ch[ji][i]) { if(sz[ch[ji][i]]<k) k-=sz[ch[ji][i]]; else { printf("%c",'a'+i); ji=ch[ji][i]; break; } } } } printf("\n"); } int main() { scanf("%s",s+1); n=strlen(s+1); scanf("%d%d",&opt,&k); for(int i=1;i<=n;++i) insert(s[i]-'a'); for(int i=1;i<=cnt;++i) a[len[i]]++; for(int i=1;i<=n;++i) a[i]+=a[i-1]; for(int i=cnt;i>=1;--i) rk[a[len[i]]--]=i; if(opt==0) { for(int i=cnt;i>=1;--i) ri[rk[i]]=1; } else { for(int i=cnt;i>=1;--i) ri[fa[rk[i]]]+=ri[rk[i]]; } ri[1]=0; for(int i=cnt;i>=1;--i) { sz[rk[i]]=ri[rk[i]]; for(int j=0;j<=25;++j) sz[rk[i]]+=sz[ch[rk[i]][j]]; } query(k); return 0; }