更通用的容斥原理

· · 算法·理论

更一般的的容斥原理

对于同一固定全集 U 上的子集 A_1,A_2,\dots,A_n\subseteq U,令 f:\mathcal P(U)\to Gf 是一个函数,其定义域为 U 的所有子集,值域为 G), 其中 G 为阿贝尔群(运算结果封闭于 G,运算有结合律、交换律,每个元素有单位元、逆元)。

若满足有限可加,即:

A\cap B=\varnothing \;\Longrightarrow\; f(A\cup B)=f(A)+f(B)

从而必有 f(\varnothing)=0

有限可加很强。其实意思就是每个 x \in G 会带个权 w_x,一个集合 S 的权值 f(S) = \sum_{x \in S} w_x,这就是充要的了。

为什么?因为根据定义,一个集合的权值可以拆成,两个不交且并为自身的集合之和,因此可以不断把 f(一个集合) 分裂成 f(\text{任意一个单元素集合}) + f(\text{剩下元素组成的集合})。其中 f(\text{剩下元素组成的集合}) 继续分拆,f(\text{任意一个单元素集合}) 直接累计进贡献。

不难发现,贡献来自包含的每个元素,因此有限可加相当于元素带权是充要的。

则有以下三条常用的容斥恒等式:

\boxed{\text{并转交}} \qquad f\left( \bigcup_{i=1}^n A_i \right) = \sum_{k=1}^n (-1)^{k+1} \sum_{1 \le i_1 < i_2 < \dots < i_k \le n} f\left(A_{i_1} \cap A_{i_2} \cap \dots \cap A_{i_k}\right) \boxed{\text{交转并}} \qquad f\left( \bigcap_{i=1}^n A_i \right) = \sum_{k=1}^n (-1)^{k+1} \sum_{1 \le i_1 < i_2 < \dots < i_k \le n} f\left(A_{i_1} \cup A_{i_2} \cup \dots \cup A_{i_k}\right) \boxed{\text{交转补的交}} \qquad f\left( \bigcap_{i=1}^n A_i \right) = \sum_{k=0}^n (-1)^k \sum_{1 \le i_1 < i_2 < \dots < i_k \le n} f\left( A_{i_1}^c \cap A_{i_2}^c \cap \dots \cap A_{i_k}^c \right)

其中 k=0 的内层和按约定为 f(U)(即空交为 U)。

看着不是很眼熟?把 f(S) 替换成 |S|,现在看懂了吗?

几乎每篇讲容斥的博客都介绍了 f(S) = |S|,即每个元素带 1 的权(不带权)时的形式;却没几篇博客讲这么一般的形式,怎么回事呢?

例题:P2429 制杖题

沿用原题面的变量名与记号。

简要题意:

求:

\left( \sum_{x=1}^{m} x\, [\, \exists \, i \in \{1, 2, \dots, n\} : p_i \mid x\,] \right) \bmod 376544743

其中 1 \leq n, m,且保证满足以下条件中的其中一条:

思路

我做容斥的第一步,总是把符合要求的统计对象组成的集合 S 表示成若干个集合的交/并的结果,其中若干个要在可控的范围内。

对于这题,S 即为“与给定质数集有交集的自然数”,答案即为 \sum_{x \in S} x

不难发现 S 可以被表示为

T_1 \cup T_2 \cup \dots \cup T_n

R_y 为所有满足 1 \leq x \leq my \mid xx 组成的集合,那么上式的 T_i 即为 R_{p_i}

因此我们要求的是 f(T_1 \cup T_2 \cup \dots \cup T_n),其中 f(U) = \sum_{x \in U} x

直接做肯定是不好做的,不然也不会用到容斥。我们使用第一个恒等式——并转交,那么有:

f\left( \bigcup_{i=1}^n T_i \right) = \sum_{k=1}^n (-1)^{k+1} \sum_{1 \le i_1 < i_2 < \dots < i_k \le n} f\left(T_{i_1} \cap T_{i_2} \cap \dots \cap T_{i_k}\right).

其中 T_{i_1} \cap T_{i_2} \cap \dots \cap T_{i_k} 的意义即为“所有满足 1 \leq x \leq m\forall \, j \in \{1, 2, \dots, k\},\, p_{i_j} \mid xx 组成的集合”,等价于 R_{\operatorname{lcm}_{1 \leq j \leq k} p_{i_j}};又因为保证 p_i 都是互不相同的素数,因此进一步简化为 R_{\prod_{j = 1}^k p_{i_j}}

$$ \sum_{\varnothing\neq S\subseteq\{1,\dots,n\}} (-1)^{|S|+1}\left(\prod_{i\in S}p_i\right)\cdot \frac{\left\lfloor \frac{m}{\prod_{i\in S}p_i}\right\rfloor\left(\left\lfloor \frac{m}{\prod_{i\in S}p_i}\right\rfloor+1\right)}{2} \pmod{376544743} $$ ## 代码 ```cpp #include <bits/stdc++.h> using namespace std; using ll = long long; using lll = __int128_t; constexpr int N = 20, P = 376544743; int p[N]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int n, m; cin >> n >> m; for (int i = 0; i < n; ++i) { cin >> p[i]; } lll ans = 0; auto dfs = [&](auto &&self, int dep, int mul, int cnt) { if (dep == n) { if (cnt) { lll res = ((m / mul + 1) * (ll)(m / mul) >> 1) * (lll)mul; ans += cnt & 1 ? res : -res; } return ; } self(self, dep + 1, mul, cnt); if ((ll)mul * p[dep] <= m) { self(self, dep + 1, mul * p[dep], cnt + 1); } }; dfs(dfs, 0, 1, 0); cout << (int)(ans % P)<< '\n'; return 0; } ``` 实际上,有了剪枝,时间复杂度一个充分紧的上界是 $\Theta(\min(2^n, nm))$,因此不需要对两个数据范围进行分别处理。