hihocoder 1466 后缀自动机六·重复旋律9

时间限制:10000ms
单点时限:2000ms
内存限制:256MB

描述

小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

输出

输出共两行,每行一个字符串(可能为空),表示答案。

如果无解则只输出一行"NO"。

样例输入
5
ab
cd
样例输出
a
cd

前5天的初始旋律分别是(“”, "c"), ("", "cd"), ("", "d"), ("a", ""), ("a", "cd”)。

小Hi:今天是一道有关字符串、博弈(组合游戏)、递推统计的题目,也是后缀自动机专题的最后一题,颇有难度,希望你能掌握。

小Ho:我想组合游戏相关的知识我在之前也学了不少,尤其是SG函数印象很深,还没忘记。

小Hi:嗯。那就好,我们在之前几次的后缀自动机专题中反复提到过后缀自动机的一个性质是从根到某个节点的路径唯一对应着原串的一个子串。

小Ho:没错,拿到这道题目里,我们可不可以这么看,选择在A或B某一个后添加一个音符还要满足仍然是子串的条件就是在各自后缀自动机上的合法转移。

小Hi:真聪明!这样我们就把这道题目的操作转化成了自动机上的转移。原题的较量实际上就变成了在自动机这个DAG(有向无环图)上进行移动。你可以看成一开始有两颗棋子,各自在A和B的后缀自动机某个节点上,每次某一个人能任选一颗棋子往后走一步,谁走不了就输了。

小Ho:这么一说还真是。关于组合游戏,我们最关心的就是怎么判断先手是否必胜。那我们也先简化原问题,如果只有一部作品,怎么判断?

小Hi:你也会活学活用了,想到简化问题的方法了。嗯,这其实就是一个DAG上的经典组合游戏,可以利用SG函数求解。

小Ho:我想起来了。DAG上每个点可以走到其他状态,根据SG函数的定义,我们假设知道了走过去的所有点的SG值,只要对这些值做一个mex操作就可以算出该点的SG值了(也就是找出没在这些值中出现的最小的非负整数),这样递归处理就能求出来了。

小Hi:很好。那两部作品呢?

小Ho:噢!我又想起来了,这就是游戏的合并!只要分别算出两个作品最终的SG值然后异或起来就是整个游戏的SG值!如果这个值为0,则先手必输,否则先手必胜。

小Hi:嗯,看来关于SG函数的博弈理论你还掌握的不错。

小Ho:谢谢夸奖!可是这道题还不止这个,我们还需要求出第K小的先手必胜态。

小Hi:恩,这个问题就比较复杂了。假设最后答案的两段旋律是sA和sB,我们需要递推求出sA的第一个字符是什么、sA的第二个字符是什么...直到求出sA,然后再递推求出sB的第一个字符是什么、sB的第二个字符是什么...直到求出sB。下面我们会以样例A="ab",B="cd"来说明一下这个过程,你会发现在这个过程中我们会反复用到一系列这样的统计数值

有多少个A(或B)的子串满足其前缀是某个给定的字符串pre,并且其SG值等于某个给定的整数x

小Hi:我们把这个数值记作cntA[pre][x](或cntB[pre][x])。特别的,我们把SG值不等于某个给定的整数x的子串数记作cntA[pre][!x](或cntB[pre][!x])

小Ho:好的。

小Hi:小Ho,你能计算出sA是空串时,有多少个sB满足(sA, sB)这个状态是先手必胜么?

小Ho:我想想啊。既然sA="",那么sgA(sA)=sgA("")=sgA(S)=2。只要sgB(sB)!=2,sB就满足先手必胜了。也就是cntB[""][!2]。

小Hi:没错,cntB[""][!2]实际等于3("c", "cd", "d")。所以sA=""的先手必胜状态一共有3个。因为我们要求K=5的状态,所以答案的sA不是空串。排除sA是空串的3个状态,我们要计算在剩余的状态中,K=2也就是第2小的状态。这时我们就要判断sA的第一个字符到底是'a'-'z'的哪个字符。

小Ho:所以我们要计算出sA[1]='a'的先手必胜态有多少、sA[1]='b'的先手必胜态有多少、sA[1]='c'的先手必胜态有多少...

小Hi:小Ho你能计算出sA[1]='a'的先手必胜态有多少么?

小Ho:应该等于ΣcntA["a"][x]*cntB[""][!x]。sA[1]='a'相当于sA前缀是"a",并且如果sA的SG值是x的话,sB的SG值就不能是x。

小Hi:没错。实际上

ΣcntA["a"][x]*cntB[""][!x] = cntA["a"][0]*cntB[""][!0] + cntA["a"][1]*cntB[""][!1] + cntA["a"][2]*cntB[""][!2] 
= 1 * 2 + 1 * 3 + 0 * 3 = 5

小Hi:所以sA第一个字符肯定是'a'了。这时我们要判断是不是sA恰好就等于"a"。sA="a"时,有多少个sB满足(sA, sB)这个状态是先手必胜?

小Ho:这个问题和一开始求sA是空串时,有多少个sB满足(sA, sB)这个状态是先手必胜一样。因为sgA["a"]=1,所以sB的数目是cntB[""][!1]=3。

小Hi:还记不记得我们现在要求的是剩余K=2的状态(前一步排除了3个sA=""的状态)。现在sA="a"就有3个状态满足先手必胜,所以我们就能确定sA="a"啦!

小Ho:没错。这时我们要在已知sA="a"的情况下,找到第K小的sB满足sgB[sB]!=sgA["a"]。

小Hi:方法也是类似的递推。先判断sB=""是不是满足先手必胜的,因为("a", "")满足先手必胜,所以我们K又要剪掉1,即K=1。

小Hi:这时我们要判断sB的第一个字符是'a'-'z'的哪一个。你会发现cntB["a"][!1]=cntB["b"][!1]=0,cntB["c"][!1]=1,所以可以确定sB第一个字符是'c'。但是("a", "c")又不是先手必胜状态,所以sB还会有第二个字符。

小Hi:这时我们要判断sB的第二个字符是'a'-'z'的哪一个。你会发现cntB["ca"][!1]=cntB["cb"][!1]=cntB["cc"][!1]=0,cntB["cd"][!1]=1,所以可以确定sB第二个字符是'd'。同时("a", "cd")是先手必胜状态,所以我们K又要剪掉1,即K=0。

小Ho:K=0说明我们找到了第K小的先手必胜状态。

小Hi:没错。所以答案就是("a", "cd")。

小Ho:大致思路我理解了,基本就是利用cntA[pre][x]和cntB[pre][x]去计数。再根据统计值和K的大小比较依次确定每一个字符。但是还有最后一个问题,cnt[pre][x]怎么求?

小Hi:值得注意的是A(或B)的子串可能有length(A)^2(或length(B)^2)那么多。所以对于每个字符串pre求cnt[pre][x]是不现实的。但是注意到对于pre1和pre2,如果它们在SAM上同属于一个状态,那么cnt[pre1][x]和cnt[pre2][x]是完全一样的。所以我们可以转而求cnt[u][x]。

小Hi:另外因为字符集只有26,所以每个点的SG值不超过26。所以我们无论是初始计算还是最终递推,复杂度都是O(N*|C|)。N表示两串的长度最大值,|C|表示字符集大小即26。

小Ho:理解了。我这就去实现一下!

  1 #include<bits/stdc++.h>
  2 using namespace std;  
  3 #define ll  long long  
  4 int const N=100000+10;  
  5 struct node{
  6     int len,fa,ch[26];  
  7 }a[N<<1],b[N<<1];     
  8 int tot1,ls1,tot2,ls2,sg1[N<<1],sg2[N<<1],used[N<<1];   
  9 ll k,cnt1[N<<1][27],cnt2[N<<1][27],sum1[N<<1],sum2[N<<1];  
 10 char s1[N],s2[N];  
 11 void add1(int c){
 12     int p=ls1;  
 13     int np=ls1=++tot1; 
 14     a[np].len=a[p].len+1;  
 15     for(;p&&!a[p].ch[c];p=a[p].fa) a[p].ch[c]=np;  
 16     if(!p) a[np].fa=1; 
 17     else {
 18         int q=a[p].ch[c];  
 19         if(a[q].len==a[p].len+1) a[np].fa=q;  
 20         else {
 21             int nq=++tot1;  
 22             a[nq]=a[q];  
 23             a[nq].len=a[p].len+1; 
 24             a[q].fa=a[np].fa=nq;  
 25             for(;p&&a[p].ch[c]==q;p=a[p].fa)
 26                 a[p].ch[c]=nq;  
 27         } 
 28     }  
 29 } 
 30 void add2(int c){
 31     int p=ls2;    
 32     int np=ls2=++tot2; 
 33     b[np].len=b[p].len+1;  
 34     for(;p&&!b[p].ch[c];p=b[p].fa) b[p].ch[c]=np;  
 35     if(!p) b[np].fa=1; 
 36     else {
 37         int q=b[p].ch[c];  
 38         if(b[q].len==b[p].len+1) b[np].fa=q;  
 39         else {
 40             int nq=++tot2;  
 41             b[nq]=b[q];  
 42             b[nq].len=b[p].len+1; 
 43             b[q].fa=b[np].fa=nq;  
 44             for(;p&&b[p].ch[c]==q;p=b[p].fa)
 45                 b[p].ch[c]=nq;  
 46         } 
 47     }  
 48 } 
 49 void dfs1(int x){
 50     if(used[x]) return;  
 51     int vis[27];  
 52     memset(vis,0,sizeof(vis));  
 53     used[x]=1;  
 54     for(int i=0;i<26;i++){
 55         int v=a[x].ch[i];  
 56         if(!v) continue; 
 57         dfs1(v);
 58         for(int j=0;j<26;j++) 
 59             cnt1[x][j]+=cnt1[v][j];  
 60         vis[sg1[v]]=1;  
 61     } 
 62     for(int i=0;i<27;i++)   
 63         if(!vis[i]){
 64             sg1[x]=i;  
 65             break;  
 66         } 
 67     cnt1[x][sg1[x]]++;  
 68 }
 69 void dfs2(int x){
 70     if(used[x])return; 
 71     int vis[27];  
 72     memset(vis,0,sizeof(vis)); 
 73     used[x]=1; 
 74     for(int i=0;i<26;i++){
 75         int v=b[x].ch[i]; 
 76         if(!v) continue;  
 77         dfs2(v); 
 78         for(int j=0;j<26;j++) 
 79             cnt2[x][j]+=cnt2[v][j];    
 80         vis[sg2[v]]=1;   
 81     } 
 82     for(int i=0;i<27;i++)
 83         if(!vis[i]){
 84             sg2[x]=i;  
 85             break;  
 86         }  
 87     cnt2[x][sg2[x]]++;  
 88 }  
 89     
 90 int main(){
 91     scanf("%lld",&k);  
 92     scanf("%s",s1);
 93     scanf("%s",s2);  
 94     int len1=strlen(s1);  
 95     int len2=strlen(s2);  
 96     tot1=tot2=ls1=ls2=1;  
 97     for(int i=0;i<len1;i++)  add1(s1[i]-'a');  
 98     for(int i=0;i<len2;i++)  add2(s2[i]-'a');  
 99     memset(used,0,sizeof(used));   
100     dfs1(1); 
101     memset(used,0,sizeof(used));  
102     dfs2(1); 
103     for(int i=1;i<=tot1;i++)  
104         for(int j=0;j<26;j++) sum1[i]+=cnt1[i][j]; 
105     for(int i=1;i<=tot2;i++) 
106         for(int j=0;j<26;j++) sum2[i]+=cnt2[i][j];  
107     ll s=0;  
108     for(int i=0;i<26;i++) {
109         s+=cnt1[1][i]*(sum2[1]-cnt2[1][i]); // s要炸long long 啊,所以我做了判断。  
110         if(s>=k) break;  
111     }    
112     if(s<k){
113         puts("NO");  
114         return 0; 
115     }  
116      int p=1,x;  
117     while (1){
118         x=sg1[p];  
119         ll num=sum2[1]-cnt2[1][x]; 
120         if(num>=k) break; 
121         else k-=num;   
122         for(int i=0;i<26;i++){
123             int v=a[p].ch[i];  
124             if(!v) continue;  
125             int xx=sg1[v];  
126             num=0;  
127             for(int j=0;j<26;j++) 
128                 num+=cnt1[v][j]*(sum2[1]-cnt2[1][j]);    
129             if(num>=k) {
130                 putchar(97+i);  
131                 p=v; break;  
132             } else k-=num; 
133         }   
134     } 
135     putchar('\n');  
136     p=1; 
137     while (1){  
138         ll num=sg2[p]==x? 0:1;  
139         if(num>=k) break;    
140         else k-=num;    
141         for(int i=0;i<26;i++) {
142             int v=b[p].ch[i]; 
143             if(!v)  continue;
144             num=0;  
145             num=sum2[v]-cnt2[v][x];      
146             if(num>=k) {
147                 putchar(97+i);     
148                 p=v;  break;  
149             } else k-=num;           
150         }  
151     }  
152     putchar('\n');  
153     return 0; 
154 }  
View Code

 

posted @ 2019-06-20 21:56  zjxxcn  阅读(133)  评论(0编辑  收藏  举报