多维计数 DP 与容斥

把每一个"限制条件"变成状态的一维;数不清楚"至少",就数"恰好"再容斥

一、计数 DP 的思维方式

计数题问"有多少种方案",答案通常巨大(要取模)。核心心法:

状态 = 到目前位置,所有"影响后续合法性"的信息;转移 = 枚举当前这一步放什么

一维不够用就加维:背包问题里"重量"是一维、"体积"又是一维;2025 年真题"员工招聘"里状态达到三维。NOI 大纲 2025 修订版新增"多维动态规划"(6 级),正是这类题的官方背书。

生活类比:统计旅行方案时,你关心的不只是"到了第几天",还有"花了多少钱、去了几个城市"——每多一个约束条件,账本上就要多记一栏。多维 DP 就是这本多栏账。
取模纪律:所有加法、乘法后立即取模;减法写成 (a - b + mod) % mod;答案最后输出前再确认一次非负。

二、容斥原理:正难则反

"至少一个条件被违反"的方案数很难直接数,但"某个指定条件被违反"往往很好数。容斥原理把它们连起来:

|合法方案| = 全部 − Σ|违反条件 i| + Σ|同时违反 i,j| − Σ|同时违反 i,j,k| + …
= ΣS (−1)|S| × "S 中条件全被违反"的方案数
全部方案 违反 A 违反 B AB |A∪B| = |A| + |B| − |A∩B|:交集被减了两次,要加回来一次
两个条件的容斥:奇数个交集相减、偶数个相加,符号 (−1)|S|

经典:P1450 硬币购物

零基础提示 · 完全背包:f[x] = 用若干种硬币(每种无限枚)凑出金额 x 的方案数。递推:依次考虑每种面值 c,for x = c..上限: f[x] += f[x−c](正序遍历 = 允许同一枚用多次)。初值 f[0] = 1("什么都不拿"算一种方案)。这是本页容斥做法的预处理部分。

完全背包走查:面值 {1, 2},金额上限 5

阶段f[0]f[1]f[2]f[3]f[4]f[5]
初始100000
放入面值 1 后111111
再放入面值 2 后112233

验证:凑 4 有 {1+1+1+1, 1+1+2, 2+2} 共 3 种 ✓。注意正序遍历让 f[x] 累加的是"已含面值 2"的 f[x−2]——同一枚 2 可以用多次,这正是"完全"背包与 0/1 背包(倒序)的区别。

问题:4 种硬币,面值 ci,第 i 种最多用 di 枚,问凑出金额 s 的方案数(多组询问)。

  1. 忽略上限:先用完全背包预处理 f[m] = 无限制凑金额 m 的方案数(只做一次)。
  2. 容斥修正:"第 i 种超限" ⇔ 先用掉 di+1 枚,即扣掉金额 (di+1)·ci 后无上限凑剩余:方案数 f[s − (di+1)ci]。
  3. 枚举超限集合 S(只有 2⁴ = 16 个):ans = Σ (−1)|S| f[s − Σi∈S(di+1)ci]。
套路提炼:把"有限制"转成"无限制预处理 + 容斥扣减",是次数/个数带上限类计数题的标准手法。"强制先用 d+1 个"这一步是灵魂——它把"超过上限"变成了可以计算的确定事件。

三、区间计数 DP:防重复计数

真题对照:P7914 括号序列——统计由括号与 * 组成的合法"超级括号序列"方案数

计数 DP 最大的坑是同一个方案被多条转移路径重复计算。防重的标准做法是给方案分类,让每类有唯一的生成方式:

自检方法:写完计数 DP,拿 n ≤ 5 的小数据暴力枚举所有方案对拍。计数题"看起来对"和"真的对"之间往往隔着一次对拍。

四、多维状态设计实例

题目类型状态维度每维的含义
二维费用背包f[i][w][v]前 i 件、重量 w、体积 v
带次数上限计数容斥 f[m]无上限方案 + 16 个子集扣减
括号序列f[l][r] / g[l][r]区间 + "两端是否匹配"类别
员工招聘(2025 T4)f[i][j][k]前 i 人、已录 j 人、某约束计数 k

设计步骤:① 写下所有限制条件 → ② 每个"影响后续选择"的条件开一维 → ③ 估算状态总数 × 转移代价是否 ≤ 10⁸ → ④ 写转移时确认"不重不漏"。

五、易错点清单

六、练习

配套小测(5 题,即时判分)