bzoj 3998: [TJOI2015]弦論【SA+二分||SAM】
阿新 • • 發佈:2019-04-16
iostream puts include -- 字典 ren cst end cpp
SA的話t==0直接預處理出每個後綴的不同串貢獻二分即可,然後t==1就按字典序枚舉後綴,然後跳右端點計算和當前後綴的前綴相同的子串個數,直到第k個
不過bzoj上會T
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=10000005; int n,o,sa[N],rk[N],he[N],wa[N],wb[N],wsu[N],wv[N],st[20][N],b[N]; long long k,a[N],sm; char s[N]; bool cmp(int r[],int a,int b,int l) { return r[a]==r[b]&&r[a+l]==r[b+l]; } void saa(char r[],int n,int m) { int *x=wa,*y=wb; for(int i=0;i<=m;i++) wsu[i]=0; for(int i=1;i<=n;i++) wsu[x[i]=r[i]]++; for(int i=1;i<=m;i++) wsu[i]+=wsu[i-1]; for(int i=n;i>=1;i--) sa[wsu[x[i]]--]=i; for(int j=1,p=1;j<=n&&p<n;j<<=1,m=p) { p=0; for(int i=n-j+1;i<=n;i++) y[++p]=i; for(int i=1;i<=n;i++) if(sa[i]>j) y[++p]=sa[i]-j; for(int i=1;i<=n;i++) wv[i]=x[y[i]]; for(int i=0;i<=m;i++) wsu[i]=0; for(int i=1;i<=n;i++) wsu[wv[i]]++; for(int i=1;i<=m;i++) wsu[i]+=wsu[i-1]; for(int i=n;i>=1;i--) sa[wsu[wv[i]]--]=y[i]; swap(x,y); p=1; x[sa[1]]=1; for(int i=2;i<=n;i++) x[sa[i]]=cmp(y,sa[i-1],sa[i],j)?p:++p; } for(int i=1;i<=n;i++) rk[sa[i]]=i; for(int i=1,j,k=0;i<=n;he[rk[i++]]=k) for(k?k--:0,j=sa[rk[i]-1];r[i+k]==r[j+k];k++); } int ques(int l,int r) { if(l>r) return n-l+1; int k=b[r-l+1]; return min(st[k][l],st[k][r-(1<<k)+1]); } int main() { scanf("%s%d%d",s+1,&o,&k); n=strlen(s+1); saa(s,n,200); for(int i=1;i<=n;i++) a[i]=a[i-1]+n-sa[i]+1-he[i]; if(o==0) { if(a[n]<k) { puts("-1"); return 0; } int l=1,r=n,ans=1; while(l<=r) { int mid=(l+r)>>1; if(a[mid]>=k) r=mid-1,ans=mid; else l=mid+1; } for(int i=sa[ans];i<=n-a[ans]+k;i++) putchar(s[i]); return 0; } b[0]=-1; for(int i=1;i<=n;i++) b[i]=b[i>>1]+1; for(int i=1;i<=n;i++) st[0][i]=he[i]; for(int i=1;i<=19;i++) for(int j=1;j<=n;j++) st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]); for(int i=1;i<=n;i++) { int nw=sa[i]+he[i],ed=n; while(1) {//cerr<<i<<" "<<ed<<endl; if(nw>n) break; if(ed==i) { if(k>n-nw+1) { k-=n-nw+1; break; } for(int j=sa[i];j<=nw+k-1;j++) putchar(s[j]); return 0; } int l=i,r=ed; while(l<=r) { int mid=(l+r)>>1; if(ques(i+1,mid)>=nw-sa[i]+1) l=mid+1,ed=mid; else r=mid-1; } if(k>ed-i+1) k-=ed-i+1; else { for(int j=sa[i];j<=nw;j++) putchar(s[j]); return 0; } nw++; } } puts("-1"); return 0; }
SAM的話就利用trie樹的性質,t==0就每個點size=1,t==1就每個點計算一下parent樹上這個點下面有幾個後綴終止點
然後計算trie樹上的子樹size和,枚舉轉移字符直到第k個即可
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1000006; int n,o,k,fa[N],ch[N][26],tot=1,cur=1,la,dis[N],wsu[N],sa[N]; long long si[N],sm[N],ans; char s[N]; void ins(int c,int id) { la=cur,dis[cur=++tot]=id; si[cur]=1; int p=la; for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=cur; if(!p) fa[cur]=1; else { int q=ch[p][c]; if(dis[q]==dis[p]+1) fa[cur]=q; else { int nq=++tot; dis[nq]=dis[p]+1; memcpy(ch[nq],ch[q],sizeof(ch[q])); fa[nq]=fa[q]; fa[q]=fa[cur]=nq; for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq; } } } int main() { scanf("%s%d%d",s+1,&o,&k); n=strlen(s+1); for(int i=1;i<=n;i++) ins(s[i]-'a',i); for(int i=1;i<=tot;i++) wsu[dis[i]]++; for(int i=1;i<=n;i++) wsu[i]+=wsu[i-1]; for(int i=tot;i>=1;i--) sa[wsu[dis[i]]--]=i; for(int i=tot;i>=1;i--) o?si[fa[sa[i]]]+=si[sa[i]]:si[sa[i]]=1; si[1]=0; for(int i=tot;i>=1;i--) { sm[sa[i]]=si[sa[i]]; for(int j=0;j<26;j++) if(ch[sa[i]][j]) sm[sa[i]]+=sm[ch[sa[i]][j]]; } if(sm[1]<k) { puts("-1"); return 0; } int nw=1; while((k-=si[nw])>0) { int w; for(int i=0;i<26;i++) if(ch[nw][i]) { if(sm[ch[nw][i]]<k) k-=sm[ch[nw][i]]; else { w=i; break; } } putchar(w+'a'); nw=ch[nw][w]; } return 0; }
bzoj 3998: [TJOI2015]弦論【SA+二分||SAM】