HihoCoder1466-後綴自動機六·重復旋律9
小Hi平時的一大興趣愛好就是演奏鋼琴。我們知道一段音樂旋律可以被表示為一段字符構成的字符串。
現在小Hi已經不滿足於單單演奏了!他通過向一位造詣很高的前輩請教,通過幾周時間學習了創作鋼琴曲的基本理論,並開始對曲目做改編或者原創。兩個月後,小Hi決定和前輩進行一場創作曲目的較量!
規則是這樣的,有兩部已知經典的作品,我們稱之為A和B。經典之所以成為經典,必有其經典之處。
剛開始,紙上會有一段A的旋律和一段B的旋律。兩人較量的方式是輪流操作,每次操作可以選擇在紙上其中一段旋律的末尾添加一個音符,並且要求添加完後的旋律依然是所在作品的旋律(也就是A或B的一個子串)。誰詞窮了(無法進行操作)就輸了。
小Hi和前輩都足夠聰明,但是小Hi還是太年輕,前輩打算教訓他一頓。前輩表示會和小Hi進行K次較量,只要小Hi贏了哪怕一次就算小Hi獲得最終勝利。但是前提是開始紙上的兩段旋律需要他定。小Hi欣然同意,並且表示每次較量都讓前輩先操作。
前輩老謀深算,顯然是有備而來。他已經洞悉了所有先手必勝的初始(兩段)旋律。第i天前輩會挑選字典序第i小的初始(兩段)旋律來和小Hi較量。那麽問題來了,作為吃瓜群眾的你想知道,最後一天即第K天,前輩會定哪兩個旋律呢?
初始時兩段旋律的字典序比較方式是先比較前一個旋律字典序,一樣大則比較後一旋律的字典序。
輸入
第一行包含一個正整數,K。K<=10^18
第二行包含一個非空的僅有小寫字母構成的字符串,表示作品A。|A|<=10^5
第三行包含一個非空的僅有小寫字母構成的字符串,表示作品B。|B|<=10^5
輸出
Sample Input
5 ab cd
Sample Output
a cd
輸出共兩行,每行一個字符串(可能為空),表示答案。
如果無解則只輸出一行"NO"。
題解:
後綴自動機的next指針DAG圖上求SG 函數值DFS找字典序第K小先手必勝子串對 ;
題目大意:給定兩個字符串A,B ,現在要求分別用A,B 的兩個子串X,Y 進行遊戲,問字典序第K KK小的兩個先手必勝的X,Y 是哪兩個.其中X,Y的字典序比較方式為先比較第一個串的字典序,後比較第二個串的字典序.
考慮直接對A,B 都建立SAM,由於是要求它們的子串勝負,所以考慮對於SAM上的每個狀態求出它的勝負,利用SG函數即可.整個遊戲的勝負可以通過SG定理來合並.
值得註意的是,很容易發現SAM上任意一個狀態的sg值都不會超過字符集大小,因為SAM上每個狀態最多連字符集大小條出邊.
得到了每一個狀態的勝負之後,我們考慮在一個SAM上如何找到第k 小的必勝子串,這個問題類似於第k小子串問題,只是我們預處理每一個狀態開始的所有子串的時候要忽略必敗態,也就是忽略sg=0 時候的情況.兩個串的時候,我們可以考慮先確定答案在第一個SAM上的位置,再確定第二個.不過在第一個SAM上跑的時候,要直接減掉可以與正在遍歷的狀態組成必勝態的子串數量,也就是說sg=? 0 ? ,這個時候我們就需要記錄sg為每一個數時的子串數量了.
#include<bits/stdc++.h> using namespace std; typedef long long ll; #define csiz 27 const int INF=0x3f3f3f3f; const int maxn=1e5+10; char s[maxn],ans1[maxn<<1],ans2[maxn<<1]; int tmp; ll k; struct SAM{ int fa[maxn<<1],nxt[maxn<<1][26],l[maxn<<1]; int last,ct,sg[maxn<<1],flag[maxn<<1][csiz]; ll sum[maxn<<1],cnt[maxn<<1][csiz]; void Init() { last=ct=1; memset(cnt,0,sizeof(cnt)); memset(sum,0,sizeof(sum)); memset(nxt[1],0,sizeof(nxt[1])); fa[1]=l[1]=0; } int NewNode() { ct++; memset(nxt[ct],0,sizeof(nxt[ct])); fa[ct]=l[ct]=0; return ct; } void Insert(int ch) { int p=last,np=NewNode(); last=np; l[np]=l[p]+1; while(p&&!nxt[p][ch]) nxt[p][ch]=np,p=fa[p]; if(!p) fa[np]=1; else { int q=nxt[p][ch]; if(l[q]==l[p]+1) fa[np]=q; else { int nq=NewNode(); memcpy(nxt[nq],nxt[q],sizeof(nxt[q])); fa[nq]=fa[q]; l[nq]=l[p]+1; fa[np]=fa[q]=nq; while(nxt[p][ch]==q) nxt[p][ch]=nq,p=fa[p]; } } } int GetSG(int k) { if(sg[k]^-1) return sg[k]; for(int i=0;i<=26;++i) flag[k][i]=0; for(int i=0;i<26;++i) if(nxt[k][i]) { flag[k][GetSG(nxt[k][i])]=1; for(int j=0;j<=26;++j) cnt[k][j]+=cnt[nxt[k][i]][j]; } int i=0; while(flag[k][i]) ++i; sg[k]=i; ++cnt[k][sg[k]]; for(int i=0;i<=26;++i) sum[k]+=cnt[k][i]; return sg[k]; } void Build(char*ss) { Init(); int len=strlen(ss); for(int i=0;i<len;++i) Insert(ss[i]-‘a‘); for(int i=1;i<=ct;++i) sg[i]=-1; GetSG(1); } }sa,sb; ll calc(int a,int b) { ll sum=0; for(int i=0;i<=26;++i) sum+=sa.cnt[a][i]*(sb.sum[b]-sb.cnt[b][i]); return sum; } int dfs1(int x,int pos) { ll sum1=sb.sum[1]-sb.cnt[1][sa.sg[x]],sum2; if(sum1>=k) return x; else k-=sum1; for(int i=0;i<26;++i) { if(sa.nxt[x][i]) { sum2=calc(sa.nxt[x][i],1); if(sum2<k) k-=sum2; else { ans1[pos]=‘a‘+i; return dfs1(sa.nxt[x][i],pos+1); } } } return -1; } int dfs2(int x,int pos) { k-=sa.sg[tmp]!=sb.sg[x]; if(k==0) return x; ll sum; for(int i=0;i<26;++i) { if(sb.nxt[x][i]) { sum=sb.sum[sb.nxt[x][i]]-sb.cnt[sb.nxt[x][i]][sa.sg[tmp]]; if(sum<k) k-=sum; else { ans2[pos]=‘a‘+i; return dfs2(sb.nxt[x][i],pos+1); } } } return -1; } int main() { scanf("%lld",&k); scanf("%s",s); sa.Build(s); scanf("%s",s); sb.Build(s); tmp=dfs1(1,1); if(tmp^-1) dfs2(1,1); if(tmp==-1) puts("NO"); else printf("%s\n%s\n",ans1+1,ans2+1); return 0; }View Code
HihoCoder1466-後綴自動機六·重復旋律9