Solution -「LOCAL」Minimal DFA
令 ,对于所有形式语言 , 的最小 DFA 状态数的最大值,以及取到这一最大值时, 的最小值和最大值。
。
引理 对于任意串 ,定义 ,当且仅当 。语言 的最小 DFA 状态数就是等价关系 在 上划分出的等价类数目。
直接考虑 的最小 DFA 的形态,可以发现:
- 对于任意状态,从开始状态转移到它的路径长度唯一;
- 不存在两个状态的转移完全相同(输入同样字符,转移向同一个状态)。
根据性质一,我们将 DFA 的 DAG 分层,那么总状态数就是每层的状态数之和。令 为第 层的状态,则 为开始状态, 为唯一接受状态,此时对于第 层,从 对 的限制来看,显然 ;从 对 的限制来看,为了满足性质二, 最多为 内的某个点提供 种转移的选择( 表示空状态,即分别枚举 和 转移向谁,不能同时为空)。根据这一构造,每个 的上界显然可以同时取到,所以第一问就解决了。
设 为最后一层满足 的状态,此后, 的大小都将由性质二的限制决定。前后都已经固定, 只取决于 的长相。
先来刻画 内每个点对应的后缀串数量。令 表示 中,状态数量关于状态对应后缀串数量的 GF,那么 ,其实就是 。
然后,讨论一下 和 的大小关系:
若 ,注意到此时必然有 , 的当务之急是让每个 都有前驱。因此先让 内每个点随便在 里连两个,剩下一个状态放弃一个转移最小化 ,增加一个到 内后缀最多的状态的转移最大化 。
若 ,我们需要同时关注“每个状态都有前驱”以及“最小/最大化 ”的要求。先描述出“在 里连两个后继”的贡献,令 表示连接方案数关于连接到后继的后缀总数的 GF,那么 。接着贪心考虑最小化与最大化 的构造方案:
- 最小化 ,先用 内的 个状态直接各自连一条转移边到 内的状态,求到剩下能选的连接方案的 GF ,在 里按指标升序贪心选择。
- 最大化 ,先用 内的 个状态直接各自连一条转移边到 内的状态,再连一条转移边到 内后缀串最多的状态,其余和最小化类似。
撇开高精度计算,复杂度是 的。
# 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()
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现