学习三种把全局约束拆开的办法:多数食材至多一个、函数调用是无环组合、超级括号串有唯一顶层分解。
前置:计数 DP、容斥、拓扑排序、区间 DP。
三题都不能把原过程原样塞进 DP:菜品选择有太多列计数,函数调用会展开成指数长序列,括号段的语法又存在多种看似等价的分割。共同突破口是先找到唯一能造成重复或超限的对象,再改用差值、DAG 系数或无歧义语法状态。
忽略平衡限制,总方案为各行 (1+rowSum) 乘积减空集。固定食材 j,DP 差值 chosen_j-chosen_other:不选保持,选 j 加一,选其他减一。最终正差值方案恰是 j 超过一半。对所有 j 求和并从总方案减去。
每个坏菜单有唯一多数食材,故被减恰一次;每个好菜单没有正差值食材,不被减。复杂度 $O(mn^2)$,约四千万量级。
total = product(1 + row_sum[i]) - 1
bad = 0
for ingredient j:
dp[difference=0] = 1
for method i:
choose_none: difference unchanged
choose j: difference += 1, weight a[i][j]
choose other: difference -= 1, weight row_sum[i]-a[i][j]
bad += sum(dp[difference > 0])
answer = total - bad
建立 caller 指向 callee 的 DAG,并新增超级函数。逆拓扑计算 mul[f]:加法函数为 1,乘法函数为 v,组合函数为子倍率乘积。正拓扑传播 coef:对每个组合函数从调用列表后向前维护 suffix,向子函数增加 coef[parent]*suffix。最后初始数组乘超级倍率,类型 1 函数在其位置加 coef*f.value。
mul[f] 由复合顺序的乘法结合律正确表示函数对旧数组的线性系数。对某次子调用,它产生的全部加法只受其后调用倍率影响;逆序 suffix 恰为这些倍率乘积。DAG 拓扑保证父系数确定后再传子,汇总所有调用上下文且不展开重复子图。
复杂度 $O(n+m+Q+\sum C)$。
add super_function calling the query sequence
topo = topological_order(caller -> callee)
for f in reverse(topo): compute mul[f]
coef[super] = 1
for f in topo:
suffix = 1
for child in reverse(calls[f]):
coef[child] += coef[f] * suffix
suffix *= mul[child]
answer = initial * mul[super]
apply every type-1 addition weighted by coef
ASB 已包含在内部合法串的顶层分解中。primitive[l][r] 表示单个最外层括号块;valid[l][r] 表示一个或多个块组成的合法串;star[l][r] 表示合法非空星段。另设 after[l][r] 表示从 l 开始的“合法串,或星段后接合法串”,用于 $O(n^3)$ 合并块间可选星段。
最外层括号匹配唯一决定 primitive。任一 valid 的第一 primitive 结束点唯一,其后要么直接接 valid,要么接唯一最大起始星段再接 valid;状态按实际字符边界分解,不按文法括分,故不重。所有生成规则的五类括号内部和两类顶层连接均被转移覆盖,故不漏。
时间 $O(n^3)$,空间 $O(n^2)$。
star[l][r] = interval can be all '*' and length <= k
for interval length increasing:
primitive[l][r] = wrap(empty / star / valid / star+valid / valid+star)
valid[l][r] = primitive[l][r]
+ sum(first_primitive * after[remaining])
after[l][r] = valid[l][r] + sum(star_prefix * valid_suffix)
《Emiya》的差值下标要整体平移,每处理一行都要从旧层转移而不得原地更新;《函数调用》要分清“节点的整体乘法系数”和“它被执行多少次”,两次拓扑的方向不同;《括号序列》建议把每个区间状态的语法含义写在数组注释里,先用长度不超过 10 的全串枚举检查是否重复计数。
三题分别利用“坏对象唯一”“调用图无环”“顶层分解唯一”。计数题开始前要先问:正在数对象,还是在数生成对象的方法?
对 n 很小分别枚举菜单、展开函数、枚举问号替换做对拍。括号题先写一个判定器,再验证 DP 不是只匹配样例。