1. 硬币购物
4 种面值的硬币,第 i 种的面值是 C i C_i Ci。 n n n 次询问,每次询问给出每种硬币的数量 D i D_i Di 和一个价格 S S S,问付款方式。 n ≤ 1 0 3 , S ≤ 1 0 5 n\leq 10^3,S\leq 10^5 n≤103,S≤105.
如果用背包做的话复杂度是 O ( 4 n S ) O(4nS) O(4nS),无法承受。这道题最明显的特点就是硬币一共只有四种。抽象模型,其实就是让我们求方程 ∑ i = 1 4 C i x i = S , x i ≤ D i \sum_{i=1}^4C_ix_i=S,x_i\leq D_i ∑i=14Cixi=S,xi≤Di 的非负整数解的个数。
采用同样的容斥方式, x i x_i xi 的属性为 x i ≤ D i x_i\leq D_i xi≤Di. 套用容斥原理的公式,最后我们要求解
∑ i = 1 4 C i x i = S − ∑ i = 1 k C a i ( D a i + 1 ) \sum_{i=1}^4C_ix_i=S-\sum_{i=1}^kC_{a_i}(D_{a_i}+1) i=1∑4Cixi=S−i=1∑kCai(Dai+1)
也就是无限背包问题。这个问题可以预处理,算上询问,总复杂度 O ( 4 S + 2 4 n ) O(4S+2^4n) O(4S+24n)。
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
ll f[N];
int T, c[5], d[5], s;
int main()
{
for(int i = 0; i < 4; i++) scanf("%d", &c[i]);
scanf("%d", &T);
f[0] = 1;
for(int i = 0; i < 4; i++)
{
for(int j = c[i]; j <= 100000; j++)
{
f[j] += f[j - c[i]];
}
}
while(T--)
{
for(int i = 0; i < 4; i++) scanf("%d", &d[i]);
scanf("%d", &s);
ll res = 0;
for(int i = 0; i < (1 << 4); i++)
{
int sum = 0, sign = 1;
for(int j = 0; j < 4; j++)
{
if(i >> j & 1) sign *= -1, sum += c[j] * (d[j] + 1);
}
if(s - sum >= 0) res += sign * f[s - sum];
}
printf("%lld\n", res);
}
}
2. 错位排列计数
对于 1 ∼ n 1\sim n 1∼n 的排列 P P P 如果满足 P i ≠ i P_i\neq i Pi=i,则称 P P P 是 n n n 的错位排列。求 n n n 的错位排列数。
很简单,离散1也讲过,答案就是 n ! ∑ k = 0 n ( − 1 ) k k ! n!\sum\limits_{k=0}^{n}\frac{(-1)^k}{k!} n!k=0∑nk!(−1)k
3. 完全图子图染色问题
A 和 B 喜欢对图(不一定连通)进行染色,而他们的规则是,相邻的结点必须染同一种颜色。今天 A 和 B 玩游戏,对于 n n n 阶 完全图 G = ( V , E ) G=(V,E) G=(V,E)。他们定义一个估价函数 F ( S ) F(S) F(S),其中 S 是边集, S ⊆ E S\subseteq E S⊆E. F ( S ) F(S) F(S) 的值是对图 G ′ = ( V , S ) G'=(V,S) G′=(V,S) 用 m m m 种颜色染色的总方案数。他们的另一个规则是,如果 ∣ S ∣ |S| ∣S∣ 是奇数,那么 A 的得分增加 F ( S ) F(S) F(S),否则 B 的得分增加 F ( S ) F(S) F(S). 问 A 和 B 的得分差值。
即求: ∑ S ⊆ E ( − 1 ) ∣ S ∣ − 1 F ( S ) \sum\limits_{S\subseteq E}(-1)^{|S|-1}F(S) S⊆E∑(−1)∣S∣−1F(S),并且要求边集不能是空集.
相邻结点染同一种颜色,我们把它当作属性。在这里我们先不遵守染色的规则,假定我们用 m 种颜色直接对图染色。对于图 G ′ = ( V , S ) G'=(V,S) G′=(V,S),我们把它当作 元素。属性 x i = x j x_i=x_j xi=xj 的含义是结点 i,j 染同色(注意,并未要求 i,j 之间有连边)。
而属性 x i = x j x_i=x_j xi=xj 对应的 集合 定义为 Q i , j Q_{i,j} Qi,j,其含义是所有满足该属性的图 G ′ G' G′ 的染色方案,集合的大小就是满足该属性的染色方案数,集合内的元素相当于所有满足该属性的图 G ′ G' G′ 的染色图。
回到题目,“相邻的结点必须染同一种颜色”,可以理解为若干个 Q Q Q 集合的交集。因此可以写出
F ( S ) = ∣ ⋂ ( i , j ) ∈ S Q i , j ∣ F(S)=\left|\bigcap_{(i,j)\in S}Q_{i,j}\right| F(S)= (i,j)∈S⋂Qi,j
上述式子右边的含义就是说对于 S 内的每一条边 ( i , j ) (i,j) (i,j) 都满足 x i = x j x_i=x_j xi=xj 的染色方案数,也就是 F ( S ) F(S) F(S).
是不是很有容斥的味道了?由于容斥原理本身没有二元组的形式,因此我们把 所有 的边 ( i , j ) (i,j) (i,j) 映射到 T = n ( n + 1 ) 2 T=\frac{n(n+1)}{2} T=2n(n+1) 个整数上,假设将 ( i , j ) (i,j) (i,j) 映射为 k , 1 ≤ k ≤ T k,1\leq k\leq T k,1≤k≤T,同时 Q i , j Q_{i,j} Qi,j 映射为 Q k Q_k Qk. 那么属性 x i = x j x_i=x_j xi=xj 则定义为 P k P_k Pk.
同时 S 可以表示为若干个 k 组成的集合,即 S ⇔ K = { k 1 , k 2 , ⋯ , k m } S\Leftrightarrow K=\{k_1,k_2,\cdots,k_m\} S⇔K={k1,k2,⋯,km}.(也就是说我们在边集与数集间建立了等价关系)。
而 E 对应集合 M = { 1 , 2 , ⋯ , n ( n + 1 ) 2 } M=\left\{1,2,\cdots,\frac{n(n+1)}{2}\right\} M={1,2,⋯,2n(n+1)}. 于是乎
F ( S ) ⇔ F ( { k i } ) = ∣ ⋂ k i Q k i ∣ F(S)\Leftrightarrow F(\{ {k_i}\})=\left|\bigcap_{k_i}Q_{k_i}\right| F(S)⇔F({ki})= ki⋂Qki
那么要求的式子展开
A n s = ∑ K ⊆ M ( − 1 ) ∣ K ∣ − 1 ∣ ⋂ k i ∈ K Q k i ∣ = ∑ i ∣ Q i ∣ − ∑ i < j ∣ Q i ∩ Q j ∣ + ∑ i < j < k ∣ Q i ∩ Q j ∩ Q k ∣ − ⋯ + ( − 1 ) T − 1 ∣ ⋂ i = 1 T Q i ∣ \begin{split} Ans &= \sum_{K\subseteq M}(-1)^{|K|-1}\left|\bigcap_{k_i\in K}Q_{k_i}\right|\\ &= \sum_{i}|Q_i|-\sum_{i<j}|Q_i\cap Q_j|+\sum_{i<j<k}|Q_i\cap Q_j\cap Q_k|-\cdots+(-1)^{T-1}\left|\bigcap_{i=1}^TQ_i\right| \end{split} Ans=K⊆M∑(−1)∣K∣−1 ki∈K⋂Qki =i∑∣Qi∣−i<j∑∣Qi∩Qj∣+i<j<k∑∣Qi∩Qj∩Qk∣−⋯+(−1)T−1 i=1⋂TQi
于是就出现了容斥原理的展开形式,因此对这个式子逆向推导
A n s = ∣ ⋃ i = 1 T Q i ∣ Ans=\left|\bigcup_{i=1}^TQ_i\right| Ans= i=1⋃TQi
再考虑等式右边的含义,只要满足 1 ∼ T 1\sim T 1∼T 任一条件即可,也就是存在两个点同色(不一定相邻)的染色方案数!而我们知道染色方案的全集是 U U U,显然 ∣ U ∣ = m n |U|=m^n ∣U∣=mn. 而转化为补集,就是求两两异色的染色方案数,即 A m n = m ! n ! A_m^n=\frac{m!}{n!} Amn=n!m!. 因此
A n s = m n − A m n Ans=m^n-A_m^n Ans=mn−Amn
解决这道题,我们首先抽象出题目数学形式,然后从题目中信息量最大的条件, F ( S ) F(S) F(S) 函数的定义入手,将其转化为集合的交并补。然后将式子转化为容斥原理的形式,并 逆向推导 出最终的结果。这道题体现的正是容斥原理的逆用。
4. 容斥原理求最大公约数为 k k k 的数对个数
设 1 ≤ x , y ≤ N 1 \le x, y \le N 1≤x,y≤N, f ( k ) f(k) f(k) 表示最大公约数为 k k k 的有序数对 ( x , y ) (x, y) (x,y) 的个数,求 f ( 1 ) f(1) f(1) 到 f ( N ) f(N) f(N) 的值。
欧拉函数(找 g c d ( x k , y k ) = 1 gcd(\frac{x}{k},\frac{y}{k}) = 1 gcd(kx,ky)=1 的数量)和莫比乌斯反演都可以写,但是不如容斥原理来得简单。
由容斥原理可以得知,先找到所有以 k k k 为 公约数 的数对,再从中剔除所有以 k k k 的倍数为 公约数 的数对,余下的数对就是以 k k k 为 最大公约数 的数对。即 f ( k ) = f(k)= f(k)= 以 k k k 为 公约数 的数对个数 − - − 以 k k k 的倍数为 公约数 的数对个数。
进一步可发现,以 k k k 的倍数为 公约数 的数对个数等于所有以 k k k 的倍数为 最大公约数 的数对个数之和。于是,可以写出如下表达式:
f ( k ) = ⌊ ( N / k ) ⌋ 2 − ∑ i = 2 i ∗ k ≤ N f ( i ∗ k ) f(k)= \lfloor (N/k) \rfloor ^2 - \sum_{i=2}^{i*k \le N} f(i*k) f(k)=⌊(N/k)⌋2−i=2∑i∗k≤Nf(i∗k)
由于当 k > N / 2 k>N/2 k>N/2 时,我们可以直接算出 f ( k ) = ⌊ ( N / k ) ⌋ 2 f(k)= \lfloor (N/k) \rfloor ^2 f(k)=⌊(N/k)⌋2,因此我们可以倒过来,从 f ( N ) f(N) f(N) 算到 f ( 1 ) f(1) f(1) 就可以了。于是,我们使用容斥原理完成了本题。
for (long long k = N; k >= 1; k--) {
f[k] = (N / k) * (N / k);
for (long long i = k + k; i <= N; i += k) f[k] -= f[i];
}
上述方法的时间复杂度为 O ( ∑ i = 1 N N / i ) = O ( N ∑ i = 1 N 1 / i ) = O ( N log N ) O( \sum_{i=1}^{N} N/i)=O(N \sum_{i=1}^{N} 1/i)=O(N \log N) O(∑i=1NN/i)=O(N∑i=1N1/i)=O(NlogN)。
附赠三倍经验供大家练手。
GCD SUM
给定 n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n≤105),求 ∑ i = 1 n ∑ j = 1 n g c d ( i , j ) \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{n}gcd(i,j) i=1∑nj=1∑ngcd(i,j).
我们可以用容斥原理求出 f ( i ) = ∑ i = 1 n ∑ j = 1 n [ g c d ( i , j ) = k ] f(i) = \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{n}[gcd(i,j)= k] f(i)=i=1∑nj=1∑n[gcd(i,j)=k] 的值,然后答案就是 ∑ i = 1 n i ∗ f ( i ) \sum\limits_{i=1}^n i * f(i) i=1∑ni∗f(i).
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
ll f[N];
int main()
{
int n;
scanf("%d", &n);
ll ans = 0;
for(int i = n; i >= 1; i--)
{
f[i] = 1LL * (n / i) * (n / i);
for(int j = 2 * i; j <= n; j += i) f[i] -= f[j];
ans += 1LL * i * f[i];
}
printf("%lld\n", ans);
return 0;
}
仪仗队
即计算 ∑ i = 1 n − 1 ∑ j = 1 n − 1 [ g c d ( i , j ) = 1 ] \sum\limits_{i=1}^{n - 1}\sum\limits_{j=1}^{n-1}[gcd(i,j)=1] i=1∑n−1j=1∑n−1[gcd(i,j)=1],最后答案要加2. 注意特判 n = 1 n = 1 n=1 的情况.
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
ll f[N];
int main()
{
int n;
scanf("%d", &n);
n--;
for(int i = n; i >= 1; i--)
{
f[i] = 1LL * (n / i) * (n / i);
for(int j = 2 * i; j <= n; j += i) f[i] -= f[j];
}
if(!n) printf("0\n");
else printf("%lld\n", f[1] + 2);
return 0;
}
能量采集
给一个 n ∗ m n * m n∗m 的方格,设某格点与点 ( 0 , 0 ) (0,0) (0,0) 连线上的点数为 k k k,则该点的值为 2 k + 1 2k+1 2k+1,求所有格点的值之和。
我们可以用容斥原理求出 f ( i ) = ∑ i = 1 n ∑ j = 1 m [ g c d ( i , j ) = k ] f(i) = \sum\limits_{i = 1}^{n}\sum\limits_{j = 1}^{m}[gcd(i,j)= k] f(i)=i=1∑nj=1∑m[gcd(i,j)=k] 的值,答案就是 ∑ i = 1 n ( 2 ∗ i − 1 ) ∗ f ( i ) \sum\limits_{i=1}^{n}(2*i-1) * f(i) i=1∑n(2∗i−1)∗f(i).
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
typedef long long ll;
ll f[N];
int main()
{
ll n, m;
scanf("%lld%lld", &n, &m);
ll ans = 0;
for(ll i = min(n, m); i; i--)
{
f[i] = (n / i) * (m / i);
for(int j = 2 * i; j <= min(n, m); j += i) f[i] -= f[j];
ans += (2 * i - 1) * f[i];
}
printf("%lld\n", ans);
return 0;
}
5. 容斥原理推导欧拉函数
考虑下面的问题:
求欧拉函数 φ ( n ) \varphi(n) φ(n)。其中 φ ( n ) = ∣ { 1 ≤ x ≤ n ∣ gcd ( x , n ) = 1 } ∣ \varphi(n)=|\{1\leq x\leq n|\gcd(x,n)=1\}| φ(n)=∣{1≤x≤n∣gcd(x,n)=1}∣。
直接计算是 O ( n log n ) O(n\log n) O(nlogn) 的,用线性筛是 O ( n ) O(n) O(n) 的,杜教筛是 O ( n 2 3 ) O(n^{\frac{2}{3}}) O(n32) 的(话说一道数论入门题用容斥做为什么还要扯到杜教筛上),接下来考虑用容斥推出欧拉函数的公式
判断两个数是否互质,首先分解质因数
n = ∏ i = 1 k p i c i n=\prod_{i=1}^k{p_i}^{c_i} n=i=1∏kpici
那么就要求对于任意 p i p_i pi, x x x 都不是 p i p_i pi 的倍数,即 p i ∤ x p_i\nmid x pi∤x. 把它当作属性,对应的集合为 S i S_i Si,因此有
φ ( n ) = ∣ ⋂ i = 1 k S i ∣ = ∣ U ∣ − ∣ ⋃ i = 1 k S i ‾ ∣ \varphi(n)=\left|\bigcap_{i=1}^kS_i\right|=|U|-\left|\bigcup_{i=1}^k\overline{S_i}\right| φ(n)= i=1⋂kSi =∣U∣− i=1⋃kSi
全集大小 ∣ U ∣ = n |U|=n ∣U∣=n,而 S i ‾ \overline{S_i} Si 表示的是 p i ∣ x p_i\mid x pi∣x 构成的集合,显然 ∣ S i ‾ ∣ = n p i |\overline{S_i}|=\frac{n}{p_i} ∣Si∣=pin,并由此推出
∣ ⋂ a i < a i + 1 S a i ∣ = n ∏ p a i \left|\bigcap_{a_i<a_{i+1}}S_{a_i}\right|=\frac{n}{\prod p_{a_i}} ai<ai+1⋂Sai =∏pain
因此可得
φ ( n ) = n − ∑ i n p i + ∑ i < j n p i p j − ⋯ + ( − 1 ) k n p 1 p 2 ⋯ p n = n ( 1 − 1 p 1 ) ( 1 − 1 p 2 ) ⋯ ( 1 − 1 p k ) = n ∏ i = 1 k ( 1 − 1 p i ) \begin{split} \varphi(n)=&n-\sum_{i}\frac{n}{p_i}+\sum_{i<j}\frac{n}{p_ip_j}-\cdots+(-1)^k\frac{n}{p_1p_2\cdots p_n}\\ =&n\left(1-\frac{1}{p_1}\right)\left(1-\frac{1}{p_2}\right)\cdots\left(1-\frac{1}{p_k}\right)\\ =&n\prod_{i=1}^k\left(1-\frac{1}{p_i}\right) \end{split} φ(n)===n−i∑pin+i<j∑pipjn−⋯+(−1)kp1p2⋯pnnn(1−p11)(1−p21)⋯(1−pk1)ni=1∏k(1−pi1)
这就是欧拉函数的数学表示啦
6. D-Double Strings_2021牛客暑期多校训练营5 (nowcoder.com)
组合数学
7. C-Cheating and Stealing_2021牛客暑期多校训练营5 (nowcoder.com)
比较难
8. Necklace of Beads - HDU 6960 - Virtual Judge (vjudge.net)
置换
9. Puzzle loop - HDU 6952 - Virtual Judge (vjudge.net)
高斯消元