弦論(tjoi2015,bzoj3998)(sam)
阿新 • • 發佈:2018-12-10
對於一個給定長度為\(N\)的字串,求它的第\(K\)小子串是什麼。
Input
第一行是一個僅由小寫英文字母構成的字串\(S\)
第二行為兩個整數\(T\)和\(K\),\(T\)為0則表示不同位置的相同子串算作一個。\(T=1\)則表示不同位置的相同子串算作多個。\(K\)的意義如題所述。
Output
輸出僅一行,為一個數字串,為第\(K\)小的子串。如果子串數目不足\(K\)個,則輸出\(-1\)
Sample Input
aabc
0 3
Sample Output
aab
Hint
\(N<=5*10^5\)
\(T<2\)
\(K<=1e9\)
題意:
中文題面,不解釋。
題解:
把串放進字尾自動機,然後處理一遍,如果\(T=0\),則所有點權為1;否則,把每個點的\(parent\)加上當前\(size\)。然後反向拓撲,像求第\(k\)大子串如這個一樣求就行了。
#include<bits/stdc++.h> using namespace std; const int N=1000010; char s[N]; int a[N],c[N]; void cmax(int &a,int b){ a=max(a,b); } void cmin(int &a,int b){ a=min(a,b); } struct SAM{ int last,cnt; int size[N],ch[N][26],fa[N<<1],l[N<<1],sum[N]; void ins(int c){ int p=last,np=++cnt;last=np;l[np]=l[p]+1; for(;p&&!ch[p][c];p=fa[p])ch[p][c]=np; if(!p)fa[np]=1; else{ int q=ch[p][c]; if(l[p]+1==l[q])fa[np]=q; else{ int nq=++cnt;l[nq]=l[p]+1; memcpy(ch[nq],ch[q],sizeof ch[q]); fa[nq]=fa[q];fa[q]=fa[np]=nq; for(;ch[p][c]==q;p=fa[p])ch[p][c]=nq; } } size[np]=1; } void build(char s[]){ int len=strlen(s+1); last=cnt=1; for(int i=1;i<=len;++i)ins(s[i]-'a'); } void calc(int op){ memset(c,0,sizeof c); for(int i=1;i<=cnt;++i)c[l[i]]++; for(int i=1;i<=cnt;++i)c[i]+=c[i-1]; for(int i=1;i<=cnt;++i)a[c[l[i]]--]=i; for(int i=cnt;i;--i){ int p=a[i],f=fa[p]; if(op){ size[f]+=size[p]; }else{ size[p]=1; } } size[1]=0; for(int i=cnt;i;--i){ int p=a[i]; sum[p]=size[p]; for(int j=0;j<26;++j){ if(ch[p][j])sum[p]+=sum[ch[p][j]]; } } } void find(int k){ int p=1; size[0]=0; while(k){ int a=0; while(k>sum[ch[p][a]]&&a<26){ if (ch[p][a]) k-=sum[ch[p][a]]; a++; } if(a>=26){ puts("-1"); return; } putchar('a'+a);k-=size[ch[p][a]]; if(k<=0)return; p=ch[p][a]; } } }sam; int main(){ cin>>s+1; sam.build(s); int t,k; cin>>t>>k; sam.calc(t); sam.find(k); }