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的字典序比较方式为先比较第一个串的字典序,后比较第二个串的字典序.
游戏规则为,先手开始游戏,两人轮流加字符,先无法操作的人输.其中每一轮操作的规则为,任选X,Y 中的一个,往后面加一个字符使得X 还是A的子串,Y 还是B的子串.
考虑直接对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; }