L. k-th Smallest Common Substring(字尾自動機)
阿新 • • 發佈:2021-11-11
GYM103145L. k-th Smallest Common Substring
給出\(n\)個字串。
詢問第\(k\)小的公共子串。
做法:
將每個字串逆序插入SAM,建出字尾樹。
第一個需求,公共子串:
這裡可以把每個字串視為一種顏色,它在SAM上對應的每個終止節點染上這個顏色。
然後問題轉化為詢問一個節點的link樹子樹內是否有n種顏色。如果有,那麼這個節點表示的子串就是公共子串。
這一步可以用樹上差分做掉。
第二個需求,第k大。
可以通過程式碼裡給出的排序方法,對字尾樹做一個字典序排序。
這樣dfs序就是字典序了。順便完成了每個節點的endpos位置的儲存。
然後將所有公共子串節點按照dfs序排序。
求一個字首和陣列,對每個詢問k,在字首和陣列上二分,二分找到對應的節點,輸出endpos位置,根據長度
這題就做完了。
#include<bits/stdc++.h> using namespace std; const int maxn=1e6+10; int nxt[maxn][26],link[maxn],len[maxn],lst=1,tot=1; int c[maxn]; pair<int,int> edp[maxn]; int n,q; vector<int> e[maxn]; void sam_extend (char c) { int cur=++tot; len[cur]=len[lst]+1; int p=lst; while (p&&!nxt[p][c-'a']) { nxt[p][c-'a']=cur; p=link[p]; } if (!p) link[cur]=1; else { int q=nxt[p][c-'a']; if (len[p]+1==len[q]) { link[cur]=q; } else { int clone=++tot; len[clone]=len[p]+1; for (int i=0;i<26;i++) nxt[clone][i]=nxt[q][i]; link[clone]=link[q]; while (p&&nxt[p][c-'a']==q) { nxt[p][c-'a']=clone; p=link[p]; } link[q]=link[cur]=clone; } } lst=cur; } vector<int> g[maxn]; int h[maxn],father[25][maxn],dfn[maxn],tol; void init () { for (int i=0;i<=tot;i++) { g[i].clear(); e[i].clear(); h[i]=dfn[i]=link[i]=len[i]=c[i]=0; edp[i]=make_pair(0,0); for (int j=0;j<26;j++) { nxt[i][j]=0; } } lst=tot=1; tol=0; } void dfs (int u) { dfn[u]=++tol; for (int v:g[u]) { father[0][v]=u; h[v]=h[u]+1; dfs(v); edp[u]=max(edp[u],edp[v]); } } int lca (int x,int y) { if (h[x]<h[y]) swap(x,y); for (int i=20;i>=0;i--) if (h[x]-h[y]>>i) x=father[i][x]; if (x==y) return x; for (int i=20;i>=0;i--) { if (father[i][x]!=father[i][y]) { x=father[i][x]; y=father[i][y]; } } return father[0][x]; } vector<int> vv; void cal (int u) { dfn[u]=++tol; for (int v:g[u]) { cal(v); c[u]+=c[v]; } } int _; long long sum[maxn]; string s[maxn]; int main () { ios::sync_with_stdio(false); cin>>_; int ff=0; if (_>1) ff=1; while (_--) { cin>>n; for (int i=n;i>=1;i--) { lst=1; cin>>s[i]; int mm=s[i].size(); for (int j=s[i].size()-1;j>=0;j--) { sam_extend(s[i][j]); e[i].push_back(lst); edp[lst]=max(edp[lst],{i,mm-j}); } } for (int i=2;i<=tot;i++) g[link[i]].push_back(i); dfs(1); for (int i=1;i<=20;i++) for (int j=1;j<=tot;j++) father[i][j]=father[i-1][father[i-1][j]]; for (int i=1;i<=n;i++) { sort(e[i].begin(),e[i].end(),[&](int x,int y) { return dfn[x]<dfn[y]; }); for (int j=0;j<e[i].size();j++) c[e[i][j]]++; for (int j=0;j<e[i].size()-1;j++) { c[lca(e[i][j],e[i][j+1])]--; } } for (int i=1;i<=tot;i++) sort(g[i].begin(),g[i].end(),[&](int x,int y) { return s[edp[x].first][s[edp[x].first].size()-edp[x].second+len[i]]<s[edp[y].first][s[edp[y].first].size()-edp[y].second+len[i]]; }); vv.clear(); tol=0; cal(1); for (int i=2;i<=tot;i++) if (c[i]==n) vv.push_back(i); sort(vv.begin(),vv.end(),[&](int x,int y) { return dfn[x]<dfn[y]; }); int m=vv.size(); for (int i=1;i<=m;i++) sum[i]=sum[i-1]+len[vv[i-1]]-len[link[vv[i-1]]]; cin>>q; while (q--) { int k; cin>>k; int L=1,R=m,pp=-1; while (L<=R) { int mid=(L+R)>>1; if (sum[mid]>=k) { pp=mid; R=mid-1; } else { L=mid+1; } } if (pp==-1) { cout<<"-1\n"; continue; } k-=sum[pp-1]; int x=vv[pp-1]; //cout<<x<<'\n'; cout<<s[edp[vv[pp-1]].first].size()-edp[vv[pp-1]].second<<" "<<s[edp[vv[pp-1]].first].size()-edp[vv[pp-1]].second+len[link[vv[pp-1]]]+k<<'\n'; } init(); } }