​LINK​

hhh这个标题害怕嘛,其实是复制的某位大佬的

假如熟练掌握了第 k k k小字典序子串的话,就不很难写


首先对两个串分别建立 S A M SAM SAM记作 s a , s b sa,sb sa,sb

那么在 S A M SAM SAM的 D A G DAG DAG上,没有出边的点是必败的,我们令其 s g = 0 sg=0 sg=0

然后这是个 D A G ! ! ! DAG!!! DAG!!!我们可以利用 S G SG SG打表得到每个节点的状态

但是题目里有两个串 a , b a,b a,b

设我们选取了 s a sa sa上的点 a a a, s b sb sb上的点 b b b

实际上已经确定了选择的子串,就是从根节点出发的路径字母

s g [ a ] ⊕ s g [ b ] sg[a]\oplus sg[b] sg[a]⊕sg[b]就是此时的 s g sg sg值,因为这满足独立游戏

所以当 ( s g [ a ] ⊕ s g [ b ] ) ! = 0 (sg[a]\oplus sg[b])!=0 (sg[a]⊕sg[b])!=0时先手必胜

那么我们统计一个 c n t [ u ] [ i ] cnt[u][i] cnt[u][i]

表示从 S A M SAM SAM的点 u u u出发能转移到多少 s g sg sg值为 i i i的状态

接下来就是利用这个数组找第 k k k小的答案了

首先我们需要确定串 a a a,所以从 s a sa sa的根节点开始 d f s dfs dfs

依次枚举 ′ a ′ − ′ z ′ 'a'-'z' ′a′−′z′,看一下儿子节点的所有获胜组合是否大于 k k k

如果大于等于 k k k,肯定就走这个儿子,否则拿 k k k减去这个儿子的答案,往下继续遍历。

那怎么求节点 i i i的获胜组合有多少种呢??

我们令 s u m [ i ] = ∑ j ∈ [ 0 , 26 ] c n t [ i ] [ j ] sum[i]=\sum\limits_{j\in[0,26]} cnt[i][j] sum[i]=j∈[0,26]∑cnt[i][j]

因为只需要 s g sg sg的异或值不为零即可,所以合法组合是

s a . s u m [ i ] ∗ s b . s u m [ 1 ] − ∑ j ∈ [ 0 , 26 ] s a . c n t [ i ] [ j ] ∗ s b . c n t [ 1 ] [ j ] sa.sum[i]*sb.sum[1]-\sum\limits_{j\in[0,26]}sa.cnt[i][j]*sb.cnt[1][j] sa.sum[i]∗sb.sum[1]−j∈[0,26]∑sa.cnt[i][j]∗sb.cnt[1][j]

就一直按照这个规则走下去即可直到节点的获胜方案大于等于 k k k

至此我们确定了串 a a a的子串

我们需要保存一下这个子串的 s g sg sg值是什么,下面记作 r e s res res

接下去就去 d f s dfs dfs确定串 b b b的子串

那现在计算获胜方案就简单了,就是 s b . s u m [ i ] − s b . c n t [ i ] [ r e s ] sb.sum[i]-sb.cnt[i][res] sb.sum[i]−sb.cnt[i][res]

一直走到 k = 0 k=0 k=0为止

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
char a[maxn],b[maxn],ans1[maxn],ans2[maxn];
ll k,res;
struct SAM
{
int fa[maxn],zi[maxn][26],len[maxn],c[maxn],rk[maxn];
ll id,ed,sg[maxn],cnt[maxn][27],sum[maxn];
SAM()
{
id = ed = 1;
memset( c,0,sizeof c );
memset( rk,0,sizeof rk );
memset( fa,0,sizeof fa );
memset( zi,0,sizeof zi );
memset( len,0,sizeof len );
memset( sg,-1,sizeof sg );
memset( cnt,0,sizeof cnt );
memset( sum,0,sizeof sum );
}
void insert(int c)
{
int p = ed, np = ++id; ed = id;
len[np] = len[p]+1;
for( ;p&&!zi[p][c];p=fa[p] ) zi[p][c] = np;
if( !p ) fa[np] = 1;
else
{
int q = zi[p][c];
if( len[q]==len[p]+1 ) fa[np] = q;
else
{
int nq = ++id;
memcpy( zi[nq],zi[q],sizeof zi[nq] );
fa[nq] = fa[q], len[nq] = len[p]+1;
fa[np] = fa[q] = nq;
for( ;p&&zi[p][c]==q;p=fa[p] ) zi[p][c] = nq;
}
}
}
int getsg(int u)
{
if( sg[u]!=-1 ) return sg[u];
int ok[27]; memset( ok,0,sizeof ok );
for(int i=0,v;i<=25;i++)
{
if( !(v=zi[u][i]) ) continue;
ok[getsg(v)] = 1;
}
for(int i=0;;i++)
if( ok[i]==0 ) return sg[u] = i;
}
void init(int u)
{
if( sum[u] ) return;
for(int i=0,v;i<=25;i++)
{
if( !(v=zi[u][i]) ) continue;
init(v);
for(int j=0;j<=26;j++) cnt[u][j] += cnt[v][j];
}
cnt[u][sg[u]]++;
for(int i=0;i<=26;i++) sum[u] += cnt[u][i];
}
void build( char a[] )
{
int len = strlen( a+1 );
for(int i=1;i<=len;i++) insert( a[i]-'a' );
getsg(1); init(1);
}
}sa,sb;
int dfs1(int x,int pos)
{
ll sum1 = sb.sum[1]-sb.cnt[1][sa.sg[x]];//当前节点获胜的方案数
if( sum1>=k ) return x;
else k -= sum1;
for(int i=0,v;i<26;i++)
{
if( !(v=sa.zi[x][i]) ) continue;
ll sum2 = sa.sum[v]*sb.sum[1];
for(int j=0;j<=26;j++)
sum2 -= sa.cnt[v][j]*sb.cnt[1][j];
if( sum2<k ) k-=sum2;
else
{
ans1[pos] = 'a'+i;
return dfs1( v,pos+1 );
}
}
return -1;
}
void dfs2(int x,int pos)
{
k -= ( sa.sg[res]!=sb.sg[x] );
if( k==0 ) return;
for(int i=0,v;i<26;i++)
{
if( !(v=sb.zi[x][i] ) ) continue;
ll sum = sb.sum[v]-sb.cnt[v][sa.sg[res]];
if( sum<k ) k-=sum;
else
{
ans2[pos] = 'a'+i;
dfs2(v,pos+1); return;
}
}
}
int main()
{
scanf("%lld%s%s",&k,a+1,b+1);
sa.build(a); sb.build(b);
res = dfs1(1,1);
if( res==-1 ) puts("NO");
else
{
dfs2(1,1);
printf("%s\n%s",ans1+1,ans2+1);
}
}