容斥 More
QOJ1284. Partition Number
给定集合 \(A\),求 \(m\) 的分拆数,不包括 \(A\) 中的元素。
\(|A| \le 500, m \le 3 \times 10^5\)。
直接容斥,我们可以选定 \(A\) 中的一些数必须用,方案就是 \(p(m - \sum_{i \in S}i)\),其中 \(p\) 是分拆数。
分拆数可以用 \(O(n \sqrt(n))\) 的算法来求,现在考虑确定了和,如何求贡献。
简单 dp,设 \(dp(i,j)\) 表示前 \(i\) 个数,和为 \(j\) 的贡献。选了偶数个贡献为正,否则为负。可以直接背包转移,可以不写第一维。
时间复杂度 \(O(m\sqrt m + nm)\)。
LOJ6077.「2017 山东一轮集训 Day7」逆序对
求长度为 \(n\),逆序对恰好为 \(k\) 对的排列个数。
\(n,k \le 10^5\)。
逆天题。
对于计数逆序对一个很好的方法:我们从一个空排列开始,将 \(1 \sim n\) 依次加入排列中,通过每次插入的位置直接更新现在的逆序对个数。
不妨设 \(x_i\) 表示插入 \(i\) 新增的逆序对,不难将问题转化成下面的形式:
然后就成了超级无敌经典容斥。
我们选一个子集 \(S\),要求 \(S\) 中全部超出限制,这样我们就计算和为 \(k - \sum_{i \in S}i\) 的不带限制版本。
但是直接枚举肯定不行,我们考虑枚举和,然后计数子集个数。
设 \(g(i, j)\) 表示 \(1 \sim n\) 选了 \(i\) 个数,和为 \(j\) 的方案数。考虑到 \(k\) 和 \(n\) 同阶,所以状态的个数是 \(O(n\sqrt n)\) 的。
接下来就是很逆天的转移了:
我们再定义一个 \(f(i,j)\) 表示 \(1 \sim n-1\) 选了 \(i\) 个数,和为 \(j\) 的方案数。
对于 \(g\),如果选了 \(1\),我们要在 \(2 \sim n\) 中选和为 \(j - 1\),可以对应到在 \(1 \sim n - 1\) 中选和为 \(j - i\) 的方案!也就是 \(f(i - 1, j - i)\)。
不选 1 的话就是 \(f(i, j - i)\)。
对于 \(f\),我们考虑所有选法 \(g(i,j)\),只用减去选了 \(n\) 的就是 \(f(i,j)\),而选了 \(n\) 的与 \(f(i-1, j - n)\) 一一对应。
所以我们可以在 \(O(n \sqrt n)\) 的时间里计算这两个,下面是本题最关键的代码:
点击查看代码
g[0][0] = f[0][0] = 1;
for (int i = 1;i <= b; i++)
for (int j = 1; j <= k; j++) {
if (j >= i)
g[i][j] = (f[i - 1][j - i] + f[i][j - i]) % mod;
f[i][j] = g[i][j];
if (j >= n)
f[i][j] = (f[i][j] - f[i - 1][j - n] + mod) % mod;
}
然后就做完了。
CSES2421 Counting Reorders
重排一个字符串,要求相邻字符不相等,求方案数。
\(n \le 5000\)
对每个位置容斥,转化成求总共 \(t\) 段,段内必须相等,段间无要求的方案数。
可以用 dp 来做,设 \(dp[i,j]\) 为前 \(i\) 个字符分了 \(j\) 段方案数,这个可以 \(O(n^2)\) 转移。
然后就没了。
51Nod 1202 子序列个数
我们不妨设 \(dp_i\) 表示前 \(i\) 个数的所有子序列(包括空串)。
我们有 \(dp_i = 2dp_{i-1}\),也就是一部分不加上 \(a_i\),一部分加上。
但是一部分加上 \(a_i\) 的会和没加上的重复,我们需要减去其贡献。
假设上个 \(a_i\) 出现的位置是 \(j\),则不难发现所有前 \(j-1\) 的子序列加上 \(a_i\) 都会重复,其他都不会重复。
于是我们让 \(dp_i = 2dp_{i-1} - dp_j\) 即可。时间复杂度 \(O(n)\)。

浙公网安备 33010602011771号