Live2D

Solution -「LOCAL」Minimal DFA

Description

  Private link.

  令 Σ={a,b},对于所有形式语言 LΣnL 的最小 DFA 状态数的最大值,以及取到这一最大值时,|L| 的最小值和最大值。

  n103

Solution

  引理 对于任意串 x,yΣ,定义 xLy,当且仅当 zΣ,xzLyzL。语言 L 的最小 DFA 状态数就是等价关系 LΣ 上划分出的等价类数目。

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

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

  根据性质一,我们将 DFA 的 DAG 分层,那么总状态数就是每层的状态数之和。令 Qi (i=0,1,,m) 为第 i 层的状态,则 Q0={qs} 为开始状态,Qm={qt} 为唯一接受状态,此时对于第 k 层,从 Qk1Qk 的限制来看,显然 |Qk|2|Qk1|;从 Qk+1Qk 的限制来看,为了满足性质二,Qk+1 最多为 Qk 内的某个点提供 (|Qk+1|+1)21 种转移的选择(1 表示空状态,即分别枚举 ab 转移向谁,不能同时为空)。根据这一构造,每个 |Qk| 的上界显然可以同时取到,所以第一问就解决了。

  设 Qp 为最后一层满足 Qp=2|Qp1| 的状态,此后,Qk 的大小都将由性质二的限制决定。前后都已经固定,|L| 只取决于 δp:Qp×ΣQp+1 的长相。

  先来刻画 Qp+1 内每个点对应的后缀串数量。令 Gk(x) (k=p+1,,m) 表示 Qk 中,状态数量关于状态对应后缀串数量的 GF,那么 Gk(x)=(Gk+1(x)+1)21,其实就是 Gk(x)=(x+1)2mk1

  然后,讨论一下 |Qp||Qp+1| 的大小关系:

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

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

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

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

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()

posted @   Rainybunny  阅读(139)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示