「模拟赛20211007」A Boring Game

题目

给定两个字符串 \(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;
}
posted @ 2021-10-07 17:43  crashed  阅读(103)  评论(1编辑  收藏  举报