BZOJ3439: Kpm的MC密码
【传送门:BZOJ3439】
简要题意:
给出n个字符串,给出ki,求出以每个字符串为后缀的字符串中的编号为ki的字符串,如果没有则输出-1
题解:
倒着把字符串插进字典树里,这样子就可以保证一个点是它的子树的所有点的后缀(字典树里一个点代表一个字符串)
然后跑一遍dfs,求dfs序,然后用主席树求出每个字符串的最下面的点的子树内第k小的值就行了
参考代码:
#include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; struct trnode { int lc,rc,c; }tr[11000000];int trlen,rt[810000]; void Link(int &u,int l,int r,int p) { if(u==0) u=++trlen; tr[u].c++; if(l==r) return ; int mid=(l+r)/2; if(p<=mid) Link(tr[u].lc,l,mid,p); else Link(tr[u].rc,mid+1,r,p); } void Merge(int &u1,int u2) { if(u1==0){u1=u2;return ;} if(u2==0) return ; tr[u1].c+=tr[u2].c; Merge(tr[u1].lc,tr[u2].lc); Merge(tr[u1].rc,tr[u2].rc); } int findkth(int u1,int u2,int l,int r,int k) { if(l==r) return l; if(tr[u1].c-tr[u2].c<k) return -1; int c=tr[tr[u1].lc].c-tr[tr[u2].lc].c,mid=(l+r)/2; if(c>=k) return findkth(tr[u1].lc,tr[u2].lc,l,mid,k); else return findkth(tr[u1].rc,tr[u2].rc,mid+1,r,k-c); } struct trie { int c[27],s; trie() { s=0; memset(c,-1,sizeof(c)); } }t[810000];int tot; char st[810000]; int L[810000],R[810000],ys[810000],z; int w[810000]; int n; int pre[810000]; void bt(int d) { int x=0,len=strlen(st+1); for(int i=len;i>=1;i--) { int y=st[i]-'a'+1; if(t[x].c[y]==-1) t[x].c[y]=++tot; x=t[x].c[y]; } if(t[x].s==0) t[x].s=d; else pre[d]=t[x].s,t[x].s=d; w[d]=x; } void dfs(int x) { L[x]=++z;ys[x]=z; if(t[x].s!=0) { Link(rt[z],1,n,t[x].s); int j=t[x].s; while(pre[j]!=0) { j=pre[j]; Link(rt[z],1,n,j); } } for(int i=1;i<=26;i++) { if(t[x].c[i]!=-1) { dfs(t[x].c[i]); } } R[x]=z; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { scanf("%s",st+1); bt(i); } z=0;dfs(0); for(int i=1;i<=z;i++) Merge(rt[i],rt[i-1]); for(int i=1;i<=n;i++) { int k; scanf("%d",&k); printf("%d\n",findkth(rt[R[w[i]]],rt[L[w[i]]-1],1,n,k)); } return 0; }
渺渺时空,茫茫人海,与君相遇,幸甚幸甚