BZOJ3998:[TJOI2015]弦论(SAM)
Description
对于一个给定长度为N的字符串,求它的第K小子串是什么。
Input
第一行是一个仅由小写英文字母构成的字符串S
第二行为两个整数T和K,T为0则表示不同位置的相同子串算作一个。T=1则表示不同位置的相同子串算作多个。K的意义如题所述。
Output
输出仅一行,为一个数字串,为第K小的子串。如果子串数目不足K个,则输出-1
Sample Input
aabc
0 3
0 3
Sample Output
aab
HINT
N<=5*10^5
T<2
K<=10^9
Solution
对于$t=0$的情况,我们将$right$集合赋值为$1$,否则就赋成正常的$right$集合大小。
因为$SAM$是一个$DAG$,所以可以对$right$集合求一个后缀和$sum$,然后用类似于平衡树查找第$k$大的方式找一下就好了。具体可以看代码,还是很好懂的……
Code
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #define N (1000009) 5 using namespace std; 6 7 int n,t,k,flag; 8 char s[N]; 9 10 struct SAM 11 { 12 int p,q,np,nq,last,cnt; 13 int fa[N],son[N][26],step[N],right[N]; 14 int d[N],sum[N]; 15 int wt[N],od[N]; 16 SAM(){last=cnt=1;} 17 18 void Insert(int x) 19 { 20 p=last; np=last=++cnt; step[np]=step[p]+1; right[np]=1; 21 while (!son[p][x] && p) son[p][x]=np, p=fa[p]; 22 if (!p) fa[np]=1; 23 else 24 { 25 q=son[p][x]; 26 if (step[p]+1==step[q]) fa[np]=q; 27 else 28 { 29 nq=++cnt; step[nq]=step[p]+1; 30 memcpy(son[nq],son[q],sizeof(son[q])); 31 fa[nq]=fa[q]; fa[q]=fa[np]=nq; 32 while (son[p][x]==q) son[p][x]=nq, p=fa[p]; 33 } 34 } 35 } 36 void Calc() 37 { 38 for (int i=1; i<=cnt; ++i) wt[step[i]]++; 39 for (int i=1; i<=n; ++i) wt[i]+=wt[i-1]; 40 for (int i=cnt; i>=1; --i) od[wt[step[i]]--]=i; 41 for (int i=cnt; i>=1; --i) 42 if (t) right[fa[od[i]]]+=right[od[i]]; 43 else right[od[i]]=1; 44 right[1]=0; 45 for (int i=cnt; i>=1; --i) 46 { 47 sum[od[i]]=right[od[i]]; 48 for (int j=0; j<26; ++j) 49 sum[od[i]]+=sum[son[od[i]][j]]; 50 } 51 } 52 void Query(int x,int k) 53 { 54 if (k<=right[x]) return; 55 k-=right[x]; 56 for (int i=0; i<26; ++i) 57 { 58 if (!son[x][i]) continue; 59 if (k<=sum[son[x][i]]) 60 { 61 flag=1; 62 printf("%c",'a'+i); 63 Query(son[x][i],k); 64 return; 65 } 66 k-=sum[son[x][i]]; 67 } 68 } 69 }SAM; 70 71 int main() 72 { 73 scanf("%s%d%d",s,&t,&k); 74 n=strlen(s); 75 for (int i=0; i<n; ++i) 76 SAM.Insert(s[i]-'a'); 77 SAM.Calc(); SAM.Query(1,k); 78 if (!flag) puts("-1"); 79 }