1. 程式人生 > >bzoj 3998 [TJOI2015]弦論——字尾自動機

bzoj 3998 [TJOI2015]弦論——字尾自動機

題目:https://www.lydsy.com/JudgeOnline/problem.php?id=3998

相同子串算多個的話,先求好 right ,然後求一個 sm 表示走到這個點之後有幾種走法,即把 DAG 上自己能走到的點的 right 都收集起來,可用拓撲序解決。

相同子串算一個的話,給 DAG 上每個節點都賦上一個1,表示走到那個節點的話算一個子串;然後把 DAG 上自己能走到的點的1都收集起來。

然後可按字典序 dfs 這個 DAG ,如果 k 在這個分支裡的話就走進去,否則 k -= sm[ ] ,然後看別的分支。

不用管 len[ ] ,因為 len[ ] 表示自己這個點管轄的許多子串,但如果自己是 dfs 地走過來,就只對應了其中一個子串,所以只用管 right 就行了。

之所以相同子串算一個的話要給複製出來的那些 nq 也賦一個1,是因為 nq 只是分了一些 q 管轄的子串;如果自己 dfs 著本應走到 q 點,但因為 nq 分管了一些而走到了 nq 點的話,自己也算走到了一個子串結尾的位置(1個而不是len個),所以應該計數1。

注意用 a[ i ] 而不是 i 。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int N=5e5+5,M=1e6+5,K=30
; int lst=1,cnt=1,go[M][K],fa[M],l[M],rt[M];ll sm[M]; int tx[N],a[M]; char ch[N]; void add(int w) { int p=lst,np=++cnt;lst=np;l[np]=l[p]+1;rt[np]=1; for(;p&&!go[p][w];p=fa[p])go[p][w]=np; if(!p)fa[np]=1; else { int q=go[p][w]; if(l[q]==l[p]+1)fa[np]=q; else {
int nq=++cnt;l[nq]=l[p]+1; fa[nq]=fa[q];fa[q]=nq;fa[np]=nq; memcpy(go[nq],go[q],sizeof go[q]); for(;go[p][w]==q;p=fa[p])go[p][w]=nq; } } } void Rsort(int n,bool T) { for(int i=1;i<=cnt;i++)tx[l[i]]++; for(int i=n-1;i>=0;i--)tx[i]+=tx[i+1]; for(int i=cnt;i;i--)a[tx[l[i]]--]=i; for(int i=1;i<=cnt;i++) { int d=a[i]; if(!T)rt[d]=1; else rt[fa[d]]+=rt[d]; } // if(T)for(int i=1;i<=cnt;i++)rt[fa[a[i]]]+=rt[a[i]];//if()!!!!///a[]!!!!!! for(int i=1;i<=cnt;i++) { int d=a[i];/// sm[d]=rt[d]; for(int j=1;j<=26;j++) if(go[d][j]) { sm[d]+=sm[go[d][j]]; } } rt[1]=0;// } int main() { scanf("%s",ch);int n=strlen(ch); for(int i=0;i<n;i++)add(ch[i]-'a'+1); int T,k; scanf("%d%d",&T,&k); Rsort(n,T); int cr=1; bool flag=0; while(1) { if(rt[cr]>=k)break; k-=rt[cr]; int ycr=cr; for(int i=1,d;i<=26;i++) if(sm[d=go[cr][i]]>=k) {putchar(i+'a'-1);cr=d;break;} else k-=sm[d]; if(cr==ycr){printf("-1");break;} } return puts(""); }