「Gym100025D」Automaton

题目

mndfa-prob.png

mndfa-restri.png

mndfa-hint.png

或者,这道题在 Gym 上也有原题,请自行找 D。

分析

比较有趣的题目,构造性很强。

首先解决偏计数的第一问。根据提示中的 Myhill-Nerode Theorem,我们知道只需要最大化等价类数量即可。考虑找一个比较常见的模型来表示一个语言 \(L\)——我们很容易想到 Trie,并且可以发现把 Trie 的边全部替换为有向边之后,我们也得到了一个描述 \(L\) 的 DFA,只不过很可能不够小而已。

那么,在 Trie 上“等价类”的含义是什么?不难发现,这就意味着区分左右儿子顺序的意义下,子树同构。那么,深度不同的结点显然不可能在一个等价类里面,则分深度讨论。在深度为 \(k\) 的这一层,Trie 上最多有 \(2^k\) 个结点,而它的子树最多有 \(2^{2^{n-k}}-1\) 种情况。所以,第 \(k\) 层等价类个数的上界为 \(\min\{2^k,2^{2^{n-k}}-1\}\),总的等价类个数的一个上界为:

\[\sum_{k=0}^n\min\{2^k,2^{2^{n-k}}-1\} \]

虽然这只是一个界,但是通过写高精度计算并输出来和大样例比对后可以发现这确实是对的。


下面解决 \(|L|\) 的最小和最大的问题。经过亲身尝试,这个时候 Trie 不再是一个很好用的模型,我们考虑回归到原背景 DFA 上,也就是已知 DFA 每层有多少个结点,再在 DFA 上连边,保证它的最小性的同时最小化/最大化路径数量。

不过,Trie 还是有点用的,因为我们可以将最小 DFA 看作是最小化过后的 Trie

仍然分层考虑。如果第 \(k+1\) 层的数量受到了 \(2^{k+1}\) 的约束,则显然第 \(k\) 层也受同样的约束,并且此时连边方式(在忽略标号时)是确定的——只有可能是第 \(k\) 层的每个结点连到第 \(k+1\) 层的两个不同结点上,使得 \(k+1\) 层的任意结点的入度都不为 0;类似地,我们可以发现如果第 \(k\) 层数量受到了 \(2^{2^{n-k}}-1\) 的约束,则第 \(k\) 层连向第 \(k+1\) 层的方式(在忽略标号时)也是确定的。

所以,现在唯一可操作的,就是 \(2^k\)\(2^{2^{n-k-1}}-1\) 这两层之间的连边。宏观地考虑一下,连边就是选一个儿子的二元组 \((x,y)\)。更进一步地,考虑 \(2^{2^{n-k-1}}-1\) 个结点构成的结点集合加上一个“空”——\(S=V\cup \{\texttt{NULL}\}\),所有儿子的情况就是 \(S^2\setminus\{(\texttt{NULL},\texttt{NULL})\}\)。此外,我们选的儿子应该满足如下要求:

  • 考虑 \(2^k\) 这一边,得到——不能有两个结点的儿子对相同,这样才能保证它们各自在不同等价类中;
  • 考虑 \(2^{2^{n-k-1}}-1\) 这一边,得到——\(V\) 中每个结点必须在儿子对中出现至少一次,这样才能保证它们全都会出现而不会变成无效状态。

此外,可以发现,连到不同的儿子,对于最终路径数有不同的贡献。我们将 \(2^k\) 中某个结点连到 \(2^{2^{n-k-1}}-1\) 中某个结点后路径增加的条数称为后面那个结点的“分数”;一个儿子对的“分数”为两个儿子分数之和。则容易写出所有儿子对的分数的生成函数,这样我们不难想到先构造若干对使得 \(2^{2^{n-k-1}}-1\) 中每个结点都被算到一次,而后再贪心地选取分高/分低的儿子对。中途需要算高精度并比大小,稍微精细一点都不会被卡常。

小结:

  1. 研究这种较为抽象的问题的时候,选择恰当的模型来具象化我们的问题和目标

  2. 多转化情景,不要纠结在同一个模型或者背景下,不然反而容易卡住;

  3. 多抽象,多转化,找出关键,缩减范围

代码

#include <cstdio>

#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 BIT = 1e9;
const int MAXN = 1e4 + 5;

struct Number {
    int a[MAXN], n;
    
    Number(): a{}, n( 0 ) {}

    Number( int x ): a{}, n( 0 ) {
        for( ; x ; a[n ++] = x % BIT, x /= BIT );
    }

    inline void Print() const {
        printf( "%d", a[n - 1] );
        per( i, n - 2, 0 ) 
            printf( "%09d", a[i] );
    }

    inline operator bool() const { 
        return n > 1 || a[0];
    }
};

int N;

inline Number operator + ( const Number &a, const Number &b ) {
    Number ret; int x = 0;
    for( int &i = ret.n ; i < a.n || i < b.n ; i ++ )
        ret.a[i] = a.a[i] + b.a[i] + x, x = ret.a[i] / BIT, ret.a[i] %= BIT;
    ret.a[ret.n ++] = x;
    while( ret.n > 1 && ! ret.a[ret.n - 1] ) ret.n --;
    return ret;
}

inline Number operator - ( const Number &a, const Number &b ) {
    Number ret = a;
    for( int i = 0 ; i < a.n ; i ++ ) {
        if( ret.a[i] < b.a[i] )
            ret.a[i + 1] --, ret.a[i] += BIT;
        ret.a[i] -= b.a[i];
    }
    while( ret.n > 1 && ! ret.a[ret.n - 1] ) ret.n --;
    return ret;
}

inline Number operator * ( const Number &a, const Number &b ) {
    Number ret; LL x, tmp;
    for( int i = 0 ; i < a.n ; i ++ ) {
        x = 0;
        for( int j = 0 ; j < b.n ; j ++ ) {
            tmp = ret.a[i + j] + 1ll * a.a[i] * b.a[j] + x;
            ret.a[i + j] = tmp % BIT, x = tmp / BIT;
        }
        ret.a[i + b.n] = x;
    }
    ret.n = a.n + b.n;
    while( ret.n > 1 && ! ret.a[ret.n - 1] ) ret.n --;
    return ret;
}

inline Number operator / ( const Number &a, const int d ) {
    Number ret; LL x = 0;
    for( int i = a.n - 1 ; ~ i ; i -- ) {
        ret.a[i] = ( x * BIT + a.a[i] ) / d;
        x = ( x * BIT + a.a[i] ) % d;
    }
    ret.n = a.n;
    while( ret.n > 1 && ! ret.a[ret.n - 1] ) ret.n --;
    return ret;
}

inline bool operator < ( const Number &a, const Number &b ) {
    if( a.n < b.n ) return true;
    if( a.n > b.n ) return false;
    for( int i = a.n - 1 ; ~ i ; i -- ) {
        if( a.a[i] < b.a[i] ) return true;
        if( a.a[i] > b.a[i] ) return false;
    }
    return false;
}

inline bool operator > ( const Number &a, const Number &b ) {
    return b < a;
}

inline bool operator == ( const Number &a, const Number &b ) {
    if( a.n != b.n ) return false;
    for( int i = a.n - 1 ; ~ i ; i -- )
        if( a.a[i] ^ b.a[i] ) return false;
    return true;
}

inline bool operator <= ( const Number &a, const Number &b ) {
    return a < b || a == b;
}

inline bool operator >= ( const Number &a, const Number &b ) {
    return a > b || a == b;
}

Number Square( int indx ) {
    Number ret = 2;
    rep( i, 1, indx )
        ret = ret * ret;
    return ret;
}

Number Qkpow( Number base, int indx ) {
    Number ret = 1;
    while( indx ) {
        if( indx & 1 ) ret = ret * base;
        base = base * base, indx >>= 1;
    }
    return ret;
}

int main() {
    freopen( "dfa.in", "r", stdin );
    freopen( "dfa.out", "w", stdout );
    scanf( "%d", &N );
    bool calc = false;
    Number e = 1;
    Number ans1 = 0, pw2 = 1, ans2 = 0, ans3 = 0, comb, lim, tims, cnt;
    for( int k = 0 ; k <= N ; k ++ ) {
        if( N - k < 31 && k >= ( 1 << ( N - k ) ) ) {
            ans1 = ans1 + Square( N - k ) - ( Number ) 1;
            if( ! calc ) {
                if( k == ( 1 << ( N - k ) ) ) {
                    ans2 = Qkpow( 2, N - k ) * Qkpow( 2, ( 1 << ( N - k ) ) - 1 );
                    ans3 = ans2 + Qkpow( 2, N - k );
                } else {
                    // 必须优先保证 2^{2^{n-k}}-1,每个结点被选中一次
                    // 这样的贡献之和为(见上面 ans2)
                    // 之后再来单独考虑
                    lim = Qkpow( 2, N - k + 1 );
                    Number hlf = Qkpow( 2, N - k ), hComb;

                    ans3 = ans2 = Qkpow( 2, N - k ) * Qkpow( 2, ( 1 << ( N - k ) ) - 1 );
                    tims = Qkpow( 2, k - 1 ) - Square( N - k ) + e, hComb = comb = 1;
                    for( int r = 0 ; ( Number ) r <= lim ; ) {
                        if( r > 0 ) {
                            cnt = ( Number ) r <= hlf ? comb - hComb : comb;
                            if( tims <= cnt ) {
                                ans2 = ans2 + tims * ( Number ) r; break;
                            } else {
                                ans2 = ans2 + cnt * ( Number ) r;
                                tims = tims - cnt;
                            }
                        }
                        if( ( Number ) ( ++ r ) <= lim )
                            comb = comb * ( lim - ( Number ) ( r - 1 ) ) / r;
                        if( ( Number ) r <= hlf )
                            hComb = hComb * ( hlf - ( Number ) ( r - 1 ) ) / r;
                        else hComb = 0;
                    }
                    ans3 = ans3 + ( Square( N - k ) - e ) * hlf;
                    tims = Qkpow( 2, k - 1 ) - Square( N - k ) + e, hComb = comb = 1;
                    for( int r = 0 ; ( Number ) r < lim ; ) {
                        cnt = ( Number ) r <= hlf ? comb - hComb : comb;
                        if( tims <= cnt ) {
                            ans3 = ans3 + tims * ( lim - ( Number ) r ); break;
                        } else {
                            ans3 = ans3 + cnt * ( lim - ( Number ) r );
                            tims = tims - cnt;
                        }
                        ++ r, comb = comb * ( lim - ( Number ) ( r - 1 ) ) / r;
                        if( ( Number ) r <= hlf )
                            hComb = hComb * ( hlf - ( Number ) ( r - 1 ) ) / r;
                        else hComb = 0;
                    }
                }
                calc = true;
            }
        }
        else ans1 = ans1 + pw2;
        pw2 = pw2 * ( Number ) 2;
    }
    ans1.Print(), putchar( ' ' );
    ans2.Print(), putchar( ' ' );
    ans3.Print(), putchar( '\n' );
    return 0;
}
posted @ 2022-03-28 20:00  crashed  阅读(64)  评论(0编辑  收藏  举报