容斥原理
容斥原理是解决这一类问题的:有 \(N\) 个集合 \(S_1,S_2,\dots,S_N\),求 \(|\bigcup \limits_{i=1}^{N}S_i|\)。
我们就能发现,所有偶数个集合的交集前面都是减号,而奇数的前面的都是加号,也就是:
\[|\bigcup \limits_{i=1}^N S_i|=\sum \limits_{k=1}^N (-1)^{k-1} \sum \limits_{1\le i_1<i_2<\dots<i_k\le N}|\bigcap \limits_{j=1}^k S_{i_j}|
\]
(具体证明在[这里](容斥原理_百度百科 (baidu.com)))
代码
for(int i = 1; i < (1 << n); ++i) {
ans += (__builtin_popcount(i) % 2 ? 1 : -1) * /* i 代表的集合之交 */;
}
HDU 6397
题目描述
给定 \(N,M,K\),求有多少个长度为 \(M\) 的序列 \(A\) 满足 \(0\le A_i < N 且 \sum \limits_{i=1}^M A_i = K\)。
思路
由于没有 \(A_i\) 的范围限制就可以直接插板法,所以考虑去除非法情况。
我们可以考虑求出有 \(x\) 个元素 \(\ge N\) 的情况数量,可以看作是有 \(x\) 个元素预先分配了 \(N\),也就是 \(C_{M}^{x}\cdot C_{K + M-1-x\cdot N}^{M-1}\) 种情况,第一个 \(C\) 是从 \(M\) 个数种选出 \(x\) 个,第二个部分是用插板法计算数量。
接着使用容斥原理求解即可。
空间复杂度 \(O(1)\),时间复杂度 \(O(M)\)。
代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int MAXN = 200001, MOD = 998244353;
int t, n, m, k, inv[MAXN], f[MAXN], Inv[MAXN], ans;
void Work() {
inv[1] = f[0] = Inv[0] = 1;
for(int i = 1; i <= 200000; ++i) {
f[i] = 1ll * f[i - 1] * i % MOD;
inv[i] = (i > 1 ? 1ll * (MOD - MOD / i) * inv[MOD % i] % MOD : 1);
Inv[i] = 1ll * Inv[i - 1] * inv[i] % MOD;
}
}
int C(ll a, ll b) {
return (a < b ? 0 : 1ll * f[a] * Inv[b] % MOD * Inv[a - b] % MOD);
}
void Solve() {
cin >> n >> m >> k;
ans = 0;
for(int i = 1; i <= m; ++i) {
ans = (ans + (1ll * C(m, i) * C(0ll + k + m - 1 - 1ll * i * n, m - 1) % MOD * (i % 2 ? 1 : -1) + MOD) % MOD) % MOD;
}
cout << (C(k + m - 1, m - 1) - ans + MOD) % MOD << "\n";
}
int main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
Work();
for(cin >> t; t--; Solve()) {
}
return 0;
}