题目

给定两个字符串 \(A,B\),对于所有的满足 \(1\le K\le \min\{|A|,|B|\}\) 的 \(K\),求出从 \(A\) 中任意选出一个长度为 \(K\) 的子串 \(X\),\(B\) 中任意选出一个长度为 \(K\) 的子串 \(Y\) 后按照字典序 \(X<Y,X=Y,X>Y\) 的概率。

对于 \(100\%\) 的数据,满足 \(1\le |A|,|B|\le 2\times 10^5\)。

分析

容易想到,子串就是后缀的前缀,因此将 \(A\) 和 \(B\) 接起来构建后缀数组。

从 \(A\) 中枚举一个后缀 \(s_a\),从 \(B\) 中枚举一个后缀 \(s_b\)。考虑两者的 LCP:\(l=\operatorname{LCP}(s_a,s_b)\),那么当 \(1\le K\le l\) 的时候,\(s_a\) 和 \(s_b\) 会贡献到 \(X=Y\) 这个部分上;当 \(l< K\) 的时候会按照大小关系贡献。

这个过程最复杂的部分为 \(l<K\) 的处理——LCP 总是变化的。为了规避这个问题,巧妙的做法是容斥


\[[X=Y]+1=[X\ge Y]+[X\le Y]\]


没错!我们可以分别计算 \(X\ge Y\) 和 \(X\le Y\) 两个部分;由于总方案数是易于计算的,我们可以算出 \(X=Y\) 的方案数,进而得到 \(X>Y\) 和 \(X<Y\) 的方案数。

以 \(X\ge Y\) 为例,我们同样考虑 \(s_a\) 和 \(s_b\)。如果 \(s_a\ge s_b\),那么无论当 \(K\) 取何值都有 \(X\ge Y\);如果 \(s_a<s_b\),那么当 \(K\le l\) 的时候会带来贡献

第一种情况有隐含的 \(\min\{s_a,s_b\}\),所以找的就是二维偏序;而第二种情况可以直接枚举 LCP——建立 height 的笛卡尔树,然后枚举。总共复杂度为 \(O(n\log n)\)。

小结:

  1. 这个容斥非常的巧妙,它提供了一个在 \(\ge,\le,=\) 之间相互转化的途径;
  2. 分析的时候小心一点——由于 \(X=Y\) 也有效,因此需要注意 \(s_a<s_b\) 是可以贡献到 \(K\le l\) 的 \(X\ge Y\) 上;
代码
#include <cstdio>
#include <cstring>

#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )

typedef long long LL;

const int MAXN = 4e5 + 5;

template<typename _T>
void read( _T &x )/*{{{*/
{
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}/*}}}*/

template<typename _T>
void write( _T x )/*{{{*/
{
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) write( x / 10 );
putchar( x % 10 + '0' );
}/*}}}*/

template<typename _T>
_T MIN( const _T a, const _T b )/*{{{*/
{
return a < b ? a : b;
}/*}}}*/

LL larg[MAXN], smal[MAXN];

int sa[MAXN], rnk[MAXN << 1], tmp[MAXN], buc[MAXN];

int num[MAXN];
char A[MAXN], B[MAXN];

int N, M, L;

inline bool Equal( const int a, const int b, const int k )
{ return rnk[a] == rnk[b] && rnk[a + k] == rnk[b + k]; }

void Build( const int n )/*{{{*/
{
rep( i, 1, n ) buc[num[i]] = 1;
rep( i, 1, 26 ) buc[i] += buc[i - 1];
rep( i, 1, n ) rnk[i] = buc[num[i]];
int lst = buc[26];
for( int k = 1 ; lst ^ n ; k <<= 1 )
{
rep( i, 0, lst ) buc[i] = 0;
rep( i, 1, n ) buc[rnk[i + k]] ++;
rep( i, 1, lst ) buc[i] += buc[i - 1];
per( i, n, 1 ) tmp[buc[rnk[i + k]] --] = i;
rep( i, 0, lst ) buc[i] = 0;
rep( i, 1, n ) buc[rnk[tmp[i]]] ++;
rep( i, 1, lst ) buc[i] += buc[i - 1];
per( i, n, 1 ) sa[buc[rnk[tmp[i]]] --] = tmp[i];
tmp[sa[1]] = lst = 1;
rep( i, 2, n )
{
lst += ! Equal( sa[i - 1], sa[i], k );
tmp[sa[i]] = lst;
}
rep( i, 1, n ) rnk[i] = tmp[i];
}
}/*}}}*/

LL Gcd( LL x, LL y ) { for( LL z ; y ; z = x, x = y, y = z % y ); return x; }

inline void Print( const LL a, const LL b )/*{{{*/
{
LL d = Gcd( a, b );
write( a / d ), putchar( '/' ), write( b / d );
}/*}}}*/

namespace Part1/*{{{*/
{
struct BIT/*{{{*/
{
int a[MAXN], n;

BIT(): a{}, n( 0 ) {}

void Init( const int N )
{ n = N; rep( i, 1, n ) a[i] = 0; }

inline void Up( int &x ) const { x += x & ( - x ); }
inline void Down( int &x ) const { x -= x & ( - x ); }
inline void Update( int x, int v ) { for( ; x <= n ; Up( x ) ) a[x] += v; }
inline int Query( int x ) const { int ret = 0; for( ; x ; Down( x ) ) ret += a[x]; return ret; }
inline int Query( const int l, const int r ) const { return Query( r ) - Query( l - 1 ); }
};/*}}}*/

BIT cntA, cntB;

void Part1()/*{{{*/
{
cntA.Init( L ), cntB.Init( L );
for( int i = 1, j = 1 ; i <= N || j <= M ; )
if( i <= N && ( j > M || N - i + 1 >= M - j + 1 ) )
{
int cnt = cntB.Query( rnk[i] + 1, L );
smal[1] += cnt, smal[N - i + 2] -= cnt;
cnt = cntB.Query( 1, rnk[i] - 1 );
larg[1] += cnt, larg[N - i + 2] -= cnt;
cntA.Update( rnk[i], 1 ), i ++;
}
else
{
int cnt = cntA.Query( rnk[j + N + 1] + 1, L );
larg[1] += cnt, larg[M - j + 2] -= cnt;
cnt = cntA.Query( 1, rnk[j + N + 1] );
smal[1] += cnt, smal[M - j + 2] -= cnt;
cntB.Update( rnk[j + N + 1], 1 ), j ++;
}
}/*}}}*/
}/*}}}*/

namespace Part2
{
int sizA[MAXN], sizB[MAXN];

int stk[MAXN], top;
int lch[MAXN], rch[MAXN];

int hei[MAXN];

void DFS( const int u )
{
int lA, lB, rA, rB;
if( ! lch[u] ) lA = sa[u - 1] <= N, lB = sa[u - 1] > N;
else DFS( lch[u] ), lA = sizA[lch[u]], lB = sizB[lch[u]];
if( ! rch[u] ) rA = sa[u] <= N, rB = sa[u] > N + 1;
else DFS( rch[u] ), rA = sizA[rch[u]], rB = sizB[rch[u]];
larg[1] += 1ll * lA * rB, larg[hei[u] + 1] -= 1ll * lA * rB;
smal[1] += 1ll * lB * rA, smal[hei[u] + 1] -= 1ll * lB * rA;
sizA[u] = lA + rA, sizB[u] = lB + rB;
}

void Part2()
{
for( int i = 1, j, k = 0 ; i <= L ; i ++ )
{
if( k ) k --;
if( rnk[i] == 1 ) continue; j = sa[rnk[i] - 1];
while( i + k <= L && j + k <= L && num[i + k] == num[j + k] ) k ++;
hei[rnk[i]] = k;
}
rep( i, 2, L )
{
while( top && hei[stk[top]] >= hei[i] )
lch[i] = stk[top --];
if( top ) rch[stk[top]] = i; stk[++ top] = i;
}
DFS( stk[top] );
}
}

int main()
{
scanf( "%s%s", A + 1, B + 1 );
N = strlen( A + 1 ), M = strlen( B + 1 );
rep( i, 1, N ) num[++ L] = A[i] - 'a'; num[++ L] = 26;
rep( i, 1, M ) num[++ L] = B[i] - 'a';
Build( L );
Part1 :: Part1();
Part2 :: Part2();
int lim = MIN( N, M );
rep( i, 1, lim )
{
larg[i] += larg[i - 1];
smal[i] += smal[i - 1];
LL all = 1ll * ( N - i + 1 ) * ( M - i + 1 );
LL sam = larg[i] + smal[i] - all;
Print( smal[i] - sam, all ), putchar( ' ' );
Print( sam, all ), putchar( ' ' );
Print( larg[i] - sam, all ), putchar( '\n' );
}
return 0;
}