• Solution 「LOCAL」Minimal DFA


    \(\mathscr{Description}\)

      Private link.

      令 \(\Sigma=\{\texttt a,\texttt b\}\),对于所有形式语言 \(L\subseteq\Sigma^n\)\(L\) 的最小 DFA 状态数的最大值,以及取到这一最大值时,\(|L|\) 的最小值和最大值。

      \(n\le10^3\)

    \(\mathscr{Solution}\)

      引理 对于任意串 \(x,y\in\Sigma^\star\),定义 \(x\equiv_Ly\),当且仅当 \(\forall z\in\Sigma^\star,xz\in L\equiv yz\in L\)。语言 \(L\) 的最小 DFA 状态数就是等价关系 \(\equiv_L\)\(\Sigma^\star\) 上划分出的等价类数目。

      直接考虑 \(L\) 的最小 DFA 的形态,可以发现:

    • 对于任意状态,从开始状态转移到它的路径长度唯一;
    • 不存在两个状态的转移完全相同(输入同样字符,转移向同一个状态)。

      根据性质一,我们将 DFA 的 DAG 分层,那么总状态数就是每层的状态数之和。令 \(Q_i~(i=0,1,\dots,m)\) 为第 \(i\) 层的状态,则 \(Q_0=\{q_s\}\) 为开始状态,\(Q_m=\{q_t\}\) 为唯一接受状态,此时对于第 \(k\) 层,从 \(Q_{k-1}\)\(Q_k\) 的限制来看,显然 \(|Q_k|\le2|Q_{k-1}|\);从 \(Q_{k+1}\)\(Q_k\) 的限制来看,为了满足性质二,\(Q_{k+1}\) 最多为 \(Q_k\) 内的某个点提供 \((|Q_{k+1}|+1)^2-1\) 种转移的选择(\(1\) 表示空状态,即分别枚举 \(\texttt a\)\(\texttt b\) 转移向谁,不能同时为空)。根据这一构造,每个 \(|Q_k|\) 的上界显然可以同时取到,所以第一问就解决了。

      设 \(Q_p\) 为最后一层满足 \(Q_p=2|Q_{p-1}|\) 的状态,此后,\(Q_k\) 的大小都将由性质二的限制决定。前后都已经固定,\(|L|\) 只取决于 \(\delta_p:Q_p\times\Sigma\rightarrow Q_{p+1}\) 的长相。

      先来刻画 \(Q_{p+1}\) 内每个点对应的后缀串数量。令 \(G_k(x)~(k=p+1,\dots,m)\) 表示 \(Q_k\) 中,状态数量关于状态对应后缀串数量的 GF,那么 \(G_k(x)=(G_{k+1}(x)+1)^2-1\),其实就是 \(G_k(x)=(x+1)^{2^{m-k}}-1\)

      然后,讨论一下 \(|Q_p|\)\(|Q_{p+1}|\) 的大小关系:

      若 \(|Q_p|<|Q_{p+1}|\),注意到此时必然有 \(|Q_{p+1}|=2|Q_p|+1\)\(\delta_p\) 的当务之急是让每个 \(q\in Q_{p+1}\) 都有前驱。因此先让 \(Q_p\) 内每个点随便在 \(Q_{p+1}\) 里连两个,剩下一个状态放弃一个转移最小化 \(|L|\),增加一个到 \(Q_{p+1}\) 内后缀最多的状态的转移最大化 \(|L|\)

      若 \(|Q_p|\ge|Q_{p+1}|\),我们需要同时关注“每个状态都有前驱”以及“最小/最大化 \(L\)”的要求。先描述出“在 \(Q_{p+1}\) 里连两个后继”的贡献,令 \(F(x)\) 表示连接方案数关于连接到后继的后缀总数的 GF,那么 \(F(x)=(G_{p+1}(x)+1)^2-1=(x+1)^{2^{m-p+1}}-1\)。接着贪心考虑最小化与最大化 \(L\) 的构造方案:

    • 最小化 \(|L|\),先用 \(Q_p\) 内的 \(|Q_{p+1}|\) 个状态直接各自连一条转移边到 \(Q_{p+1}\) 内的状态,求到剩下能选的连接方案的 GF \(F_1(x)\),在 \(F_1(x)\) 里按指标升序贪心选择。
    • 最大化 \(|L|\),先用 \(Q_p\) 内的 \(|Q_{p+1}|\) 个状态直接各自连一条转移边到 \(Q_{p+1}\) 内的状态,再连一条转移边到 \(Q_{p+1}\) 内后缀串最多的状态,其余和最小化类似。

      撇开高精度计算,复杂度是 \(\mathcal O(n)\) 的。

    \(\mathcal{Code}\)

    # Rainybunny #
    
    import sys
    
    if __name__ == "__main__":
        sys.stdin = open('dfa.in', 'r')
        sys.stdout = open('dfa.out', 'w')
    
        n = int(input())
        ans = [0 for _ in range(3)]
    
        m = 0
        while 1 << m + 1 <= n - m - 1: m += 1
        ## the last limited level's G.F. is f_m(x)=(x+1)^{2^m}-1.
        # sys.stderr.write(str(m) + '\n')
    
        ans[0] = (1 << n - m) - 1
        for i in range(0, m + 1):
            ans[0] += (1 << (1 << i)) - 1
    
        lef = 1 << n - m - 1; rig = (1 << (1 << m)) - 1
        if (lef < rig):
            sys.stderr.write('type 1\n')
            ans[1] = 1 << ((1 << m) + m - 1)
            ans[2] = ans[1] + (1 << m)
        else:
            sys.stderr.write('type 2\n')
            ## get f_m(x)(->bino[0]) and f_m^2(x)(->bino[1])
            bino = [[0 for _ in range((1 << m) + 1)],
              [0 for _ in range((1 << m + 1) + 1)]]
            bino[0][0] = bino[1][0] = 1
            for i in range(1, (1 << m) + 1):
                bino[0][i] = bino[0][i - 1] * ((1 << m) - i + 1) // i
            for i in range(1, (1 << m + 1) + 1):
                bino[1][i] = bino[1][i - 1] * ((1 << m + 1) - i + 1) // i
    
            ## get ans[1].
            for i in range(1, (1 << m) + 1):
                ans[1] += i * bino[0][i]
                bino[1][i] -= bino[0][i]
            rest = lef - rig; cur = 1
            while rest and rest > bino[1][cur]:
                ans[1] += bino[1][cur] * cur
                rest -= bino[1][cur]
                cur += 1
            ans[1] += cur * rest
    
            ## get ans[2].
            for i in range(1, (1 << m) + 1):
                ans[2] += bino[0][i] * (i + (1 << m))
                bino[1][i] += bino[0][i] # recover it
                bino[1][i + (1 << m)] -= bino[0][i]
            rest = lef - rig; cur = 1 << m + 1
            while rest and rest > bino[1][cur]:
                ans[2] += bino[1][cur] * cur
                rest -= bino[1][cur]
                cur -= 1
            ans[2] += cur * rest
    
        print("%d %d %d" % (ans[0], ans[1], ans[2]))
    
        sys.stdin.close()
        sys.stdout.close()
    
    
  • 相关阅读:
    java soket 和nio
    面试题中问到 aop di ioc 怎么回答
    细谈hashmap
    java中length和length()还有size()的区别
    MySQL 中实现可重复读(RR)的原理--MVCC
    关于字节流/字符流操作文件的问题
    MySQL 中索引优化(即避免索引失效)
    MySQL 数据库中索引的实现 和 建立索引的原则
    CAS和ABA问题
    Volatile的简单理解
  • 原文地址:https://www.cnblogs.com/rainybunny/p/16036978.html
Copyright © 2020-2023  润新知