数学专题训练2 组合计数

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 n103,S105​.

如果用背包做的话复杂度是 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,xiDi 的非负整数解的个数。

采用同样的容斥方式, x i x_i xi 的属性为 x i ≤ D i x_i\leq D_i xiDi. 套用容斥原理的公式,最后我们要求解

∑ 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=14Cixi=Si=1kCai(Dai+1)

也就是无限背包问题。这个问题可以预处理,算上询问,总复杂度 O ( 4 S + 2 4 n ) O(4S+2^4n) O(4S+24n)

#include
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 1n 的排列 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=0nk!(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 SE​. 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) SE(1)S1F(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)SQi,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,1kT,同时 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\} SK={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})= kiQki

那么要求的式子展开

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_{iAns=KM(1)K1 kiKQki =iQii<jQiQj+i<j<kQiQjQk+(1)T1 i=1TQi

于是就出现了容斥原理的展开形式,因此对这个式子逆向推导

A n s = ∣ ⋃ i = 1 T Q i ∣ Ans=\left|\bigcup_{i=1}^TQ_i\right| Ans= i=1TQi

再考虑等式右边的含义,只要满足 1 ∼ T 1\sim T 1T 任一条件即可,也就是存在两个点同色(不一定相邻)的染色方案数!而我们知道染色方案的全集是 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=mnAmn

解决这道题,我们首先抽象出题目数学形式,然后从题目中信息量最大的条件, F ( S ) F(S) F(S) 函数的定义入手,将其转化为集合的交并补。然后将式子转化为容斥原理的形式,并 逆向推导 出最终的结果。这道题体现的正是容斥原理的逆用。

4. 容斥原理求最大公约数为 k k k 的数对个数

1 ≤ x , y ≤ N 1 \le x, y \le N 1x,yN 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)2i=2ikNf(ik)

由于当 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(Ni=1N1/i)=O(NlogN)

附赠三倍经验供大家练手。

GCD SUM

给定 n ( n ≤ 1 0 5 ) n(n \le 10^5) n(n105),求 ∑ 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=1nj=1ngcd(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=1nj=1n[gcd(i,j)=k]​​ 的值,然后答案就是 ∑ i = 1 n i ∗ f ( i ) \sum\limits_{i=1}^n i * f(i) i=1nif(i)​.

#include
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;
}

仪仗队

数学专题训练2 组合计数_第1张图片

即计算 ∑ 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=1n1j=1n1[gcd(i,j)=1]​​,最后答案要加2. 注意特判 n = 1 n = 1 n=1​ 的情况.

#include
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 nm 的方格,设某格点与点 ( 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=1nj=1m[gcd(i,j)=k]​​ 的值,答案就是 ∑ i = 1 n ( 2 ∗ i − 1 ) ∗ f ( i ) \sum\limits_{i=1}^{n}(2*i-1) * f(i) i=1n(2i1)f(i).​​

#include
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)={1xngcd(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=1kpici

那么就要求对于任意 p i p_i pi x x x 都不是 p i p_i pi 的倍数,即 p i ∤ x p_i\nmid x pix. 把它当作属性,对应的集合为 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=1kSi =U i=1kSi

全集大小 ∣ U ∣ = n |U|=n U=n,而 S i ‾ \overline{S_i} Si 表示的是 p i ∣ x p_i\mid x pix 构成的集合,显然 ∣ 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 ai<ai+1Sai =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φ(n)===nipin+i<jpipjn+(1)kp1p2pnnn(1p11)(1p21)(1pk1)ni=1k(1pi1)

这就是欧拉函数的数学表示啦

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)

高斯消元

你可能感兴趣的:(ACM题目整理,算法)