【洛谷】P3975 [TJOI2015]弦論(SAM)
阿新 • • 發佈:2022-03-20
題意
對於一個給定長度為 \(N\) 的字串,求它的第 \(K\) 小子串。
資料範圍
\(1\leq n \leq 10^5\),\(0\leq t \leq 1\),\(1\leq k \leq 10^9\)。
\(t\) 為 \(0\) 則表示不同位置的相同子串算作一個,\(t\) 為 \(1\) 則表示不同位置的相同子串算作多個。
思路
考慮字尾自動機(下文預設已經知曉 SAM 的基本性質)。
根據題意,當 \(t=0\) 時,SAM 上的每個節點的 \(Endpos\) 集合大小(該集合代表該節點對應的所有子串在原串中所有出現的位置)都為 \(1\)。反之則可以根據 parent 樹的性質從下往上遞推。定義 \(i\)
定義 \(sum[i]\) 表示經過 \(i\) 節點的子串數目(嚴格來說並不是,往後看就明白了)。即當前節點的 \(siz\) 加上以當它為字首的所有節點的 \(siz\)。可以在 SAM 的 DAG 上逆序遞推。需要注意的是,這裡不需要考慮 \(i\) 節點對應的所有子串,因為接下來查詢的時候根據字典序的性質是從前往後推的,也就是一個字首不斷往後擴充套件,而不是字尾往前擴充套件(舉個簡單的例子,當前節點對應的子串集合為 {\(aabb,abb,bb\)},那麼後面查詢到這裡時可能是從 \(aab\) 過來的,也可能是從 \(b\)
查詢的時候就是從根節點往後推,根據當前節點的 \(siz\) 和 \(sum\) 判斷是否要繼續往後推,具體實現可以見程式碼,這部分放在程式碼裡將更清晰。
code
#include<cstring> #include<algorithm> using namespace std; const int N=1e6+10; #define LL long long struct SAM{int ch[26],len,fa;}node[N]; int c[N],last=1,tot=1,t,k,num[N];LL siz[N],sum[N]; char s[N]; void extend(int c) { int p=last,np=last=++tot;node[np].len=node[p].len+1;siz[np]=1; while(p&&!node[p].ch[c]) node[p].ch[c]=np,p=node[p].fa; if(!p) node[np].fa=1; else { int q=node[p].ch[c]; if(node[p].len+1==node[q].len) node[np].fa=q; else { int nq=++tot;node[nq]=node[q];node[nq].len=node[p].len+1; node[q].fa=node[np].fa=nq; while(p&&node[p].ch[c]==q) node[p].ch[c]=nq,p=node[p].fa; } } } void print(int now,int k) //k表示當前還要查詢的第k小子串 { if(k<=siz[now]) return ; //如果當前節點出現次數不小於k,就代表查詢子串就是當前已經走到的子串,不需要往後推 k-=siz[now]; for(int i=0;i<26;i++) //根據字典序的性質, 從小到大列舉當前位字元 { int t=node[now].ch[i];if(!t) continue; if(sum[t]<k) k-=sum[t];//有點類似於splay查詢右子樹時減去左子樹的size else {printf("%c",i+'a');print(t,k);return ;} } } int main() { scanf("%s%d%d",s+1,&t,&k);for(int i=1;s[i];i++) extend(s[i]-'a'); for(int i=1;i<=tot;i++) c[node[i].len]++;//求siz和sum陣列可以按照這裡的基數排序的方式,也可以直接建圖dfs for(int i=1;i<=tot;i++) c[i]+=c[i-1]; for(int i=1;i<=tot;i++) num[c[node[i].len]--]=i; for(int i=tot;i>=1;i--) siz[node[num[i]].fa]+=siz[num[i]]; for(int i=1;i<=tot;i++) t?sum[i]=siz[i]:siz[i]=sum[i]=1; siz[1]=sum[1]=0;//需要注意,根節點沒有任何資訊,下面的sum[1]是判斷無解 for(int i=tot;i>=1;i--) for(int j=0;j<26;j++) sum[num[i]]+=sum[node[num[i]].ch[j]]; if(sum[1]<k) puts("-1"); else print(1,k),puts(""); return 0; }```