1. 程式人生 > >[bzoj3998][TJOI2015]弦論

[bzoj3998][TJOI2015]弦論

bits 統計 sca zoj 後綴自動機 put con 排序 val

後綴自動機絲薄題。

求給定字符串$s$的第$k$大的子串。分unique之後的和不unique的兩種詢問。


首先構建出SAM。

相同子串算一個的情況:

SAM上所有路徑組成字符串$s$的全部子串,每個狀態向下不管怎麽走,形成的串都是以當前狀態為前綴的。(廢話)

所以我們只要知道以當前串為前綴的串有多少,不就可以像在平衡樹上搜索一樣找到第$k$大嗎。

即從當前點開始,向下有多少路徑。拓撲跑一下就行了。$sum[u]=\sum sum[v] +1$。

另一種情況:

思路和第一種完全一樣,區別在於所在位置不同的相同子串本質不同了。我們發現$right$集合的大小不就是當前狀態的有多少個嗎。所以我們先求出每個點$u$的$right$集合有多大($u$的$right$集合就是所有以$u$為$parent$(也有叫$link$,我叫$fa$)的點的$right$集合的並集呀),一個道理,拓撲跑一下。$sum[u]=\sum sum[v]+|right(u)|$

代碼可以簡化的地方很多。。

看大佬們的代碼都不用dfs去統計,蒟蒻想了半天發現自己傻掉了。因為我們知道不管在SAM的DAG中,還是在$parent$樹中,兒子的$len$($max$?就是麗潔神犇ppt裏的$max$)都比父親大(DAG上說父子的話不太嚴密,但意思就是那樣)。於是按$len$排序(註意可以基數排序!)然後再轉移就一定不會錯啦。

#include<bits/stdc++.h>
using namespace std;
const int N=1000010;
typedef long long ll;
int len[N],fa[N],ch[N][26];
int las,sz;
int opt,n,val[N],sum[N],v[N],q[N]; void ins(int c){ int now=++sz;len[now]=len[las]+1; int p,q;val[now]=1; for(p=las;~p&&!ch[p][c];p=fa[p]) ch[p][c]=now; if(!~p)fa[now]=0; else{ q=ch[p][c]; if(len[q]==len[p]+1) fa[now]=q; else{
int r=++sz; fa[r]=fa[q],len[r]=len[p]+1; for(int i=0;i<26;i++) ch[r][i]=ch[q][i]; for(;~p&&ch[p][c]==q;p=fa[p]) ch[p][c]=r; fa[now]=fa[q]=r; } } las=now; } void init(){ for(int i=0;i<=sz;i++)v[len[i]]++; for(int i=1;i<=n;i++)v[i]+=v[i-1]; for(int i=sz;~i;i--) q[v[len[i]]--]=i; for(int i=sz+1;i;i--){ int t=q[i]; if(opt==1)val[fa[t]]+=val[t]; else val[t]=1; } val[0]=0; for(int i=sz+1;i;i--){ int t=q[i];sum[t]=val[t]; for(int j=0;j<26;j++) sum[t]+=sum[ch[t][j]]; } } void dfs(int u,int rk){ if(rk<=val[u])return; rk-=val[u]; for(int i=0;i<26;i++) if(ch[u][i]){ int t=ch[u][i]; if(rk<=sum[t]){ putchar(i+a); dfs(t,rk); return; } rk-=sum[t]; } } void solve(){ init(); int k;scanf("%d",&k); dfs(0,k); } char s[N]; int main(){ scanf("%s",s);fa[0]=-1; n=strlen(s); for(int i=0;i<n;i++) ins(s[i]-a); scanf("%d",&opt); solve(); }

[bzoj3998][TJOI2015]弦論