CC++ & Algorithm

加法与乘法原理的联合使用

较难6
语言版本:C++Python
概述:在复杂问题中,我们常常需要先分类再用乘法,或者先分步再用加法,综合运用两个原理。

加法与乘法原理一起用,搞定复杂计数问题

生活中,我们经常要数一数有多少种不同的选法、搭配方式或排列顺序。如果问题很简单,只用一个加法原理(分类相加)或乘法原理(分步相乘)就能算出来。但遇到复杂的情况,就需要两个原理手拉手一起上——先分类、再分步,或者先分步、再分类。学会了这个,你就能轻松解决很多有趣的计数问题,比如点餐选套餐、搭配衣服、统计比赛结果等等。

下面我们先从一个小例子说起,看看加法与乘法是怎样配合使用的。


? 分类与分步:两个原理的第一次合作

问题: 学校要选一名主持人,可以选“一男一女搭档”(两个人一起主持),也可以只选“一个男生”。主持人的备选名单里有3名男生和2名女生。问一共有多少种不同的选法?

第一步:先分类

我们把所有可能的选法分成两类:

  • 第一类:一男一女搭档(两个人)
  • 第二类:单独一个男生(一个人)

这两类不会重复,因为第一类是两个人,第二类是一个人,完全不同。所以总数就是第一类的数目 + 第二类的数目(加法原理)。

第二步:每一类内部再用乘法

  • 第一类:要同时选一个男生一个女生。这是两个步骤:先选男生(3种),再选女生(2种)。根据乘法原理,一共有 3 × 2 = 6 种。
  • 第二类:只选一个男生,就是直接从3个男生中选一个,有3种。

最后,总数 = 6 + 3 = 9 种。

我们用C++程序来模拟一下,让电脑帮我们数一数:

#include <iostream>
using namespace std;

int main() {
    int total = 0;   // 记录总选法数
    
    // 第一类:一男一女(分两步:先选男生,再选女生)
    for (int boy = 1; boy <= 3; boy++) {         // 男生编号1~3
        for (int girl = 1; girl <= 2; girl++) {  // 女生编号1~2
            total++;  // 每找到一对就加1
        }
    }
    
    // 第二类:只选一个男生(一步完成)
    for (int boy = 1; boy <= 3; boy++) {         // 每个男生单独算一种
        total++;  // 每个男生都是一种选法
    }
    
    cout << "一共有 " << total << " 种选法" << endl;
    return 0;
}

运行结果输出 9,和我们算的一模一样。


? 更多生活中的例子

例1:搭配早餐

早餐店有3种面包(奶油、红豆、肉松)和2种饮料(牛奶、豆浆)。你想买一个面包和一杯饮料,或者只买一个面包。问有多少种不同的购买方案?

  • 分类
    • 第一类:买面包+饮料(两个东西)
    • 第二类:只买一个面包(一个东西)
  • 第一类:选面包3种 × 选饮料2种 = 6种
  • 第二类:选面包3种 = 3种
  • 总数:6 + 3 = 9种

你会发现,这和主持人问题结构完全一样,只是换了物品名称。

例2:选择课外活动

学校有篮球、足球、乒乓球3种体育项目,还有合唱、绘画2种艺术项目。小明要选一个体育项目一个艺术项目,或者只选一个体育项目。多少种选择?

  • 分类同上,结果也是 3×2 + 3 = 9 种。

例3:稍微变一变——选一男一女选一男一女再加一个男生(三人组)

假如主持人要求:可以是“一男一女搭档”,也可以是“一男一女再加一个男生(共三人)”。备选名单里仍然是3男2女。问多少种选法?

  • 第一类:一男一女 → 3×2 = 6种
  • 第二类:三人组(一男一女 + 再加一个男生)。注意:这里“再加一个男生”是在已有的一男一女基础上再加人吗?实际上,三人组需要选1个男生和1个女生,然后再从剩下的男生里选一个?不对,因为三人组要包含两个男生和一个女生。所以更清楚的做法是:先分类为“两人组”和“三人组”,然后在三人组内部,需要选2个男生和1个女生。

三人组计数:选2个男生(组合问题)和1个女生。选2个男生有 C(3,2)=3 种(假设男生编号1,2,3,选哪两个?有3种:{1,2}、{1,3}、{2,3}),选1个女生有2种。所以三人组有 3×2 = 6 种。注意这里用到了乘法原理(先选男生组合,再选女生)。最后总数 = 6 + 6 = 12种。

重点:当分类内部需要分步时,就用乘法;当多个类别之间用加法。这个思路可以解决很多复杂问题。


⚠️ 新手容易犯的错误

  1. 分类重复或遗漏
    比如上面主持人问题,如果把“一男一女”和“单独一个男生”分在一类,就会乱算。必须确保每一类之间互不重叠,且覆盖所有可能。

  2. 分步顺序搞错
    在乘法原理中,每一步的选项数必须独立。比如先选男生再选女生,顺序不影响结果。但如果选人时不能重复选同一个,就要小心。比如从3个男生中选2个当正副班长,第一步选正班长3种,第二步选副班长2种(除去已选的人),这才是正确的乘法。

  3. 忘记分类
    有些问题看起来能用乘法,但实际上需要先分类。比如“选一个主持人,可以是男生或女生”看起来是两步?不,这是分类:选男生或女生,每类内部再用乘法?其实这里直接加法:男生3种 + 女生2种 = 5种。如果你错误地用乘法:3×2=6,就多算了(因为不能同时选男和女)。

  4. 循环模拟时漏掉某些情况
    用编程验证时,要确保循环覆盖所有情况。比如主持人问题,第一类用两层循环,第二类用一层循环,没有遗漏。


? 完整示例:用C++模拟更多变式

下面我们写一个完整程序,模拟“选一男一女搭档 选一男一女再加一个男生(三人组)”的情况,并统计结果。

#include <iostream>
using namespace std;

int main() {
    int total = 0;          // 总选法数
    int boys = 3;           // 男生人数
    int girls = 2;          // 女生人数
    
    // 第一类:一男一女搭档
    for (int boy = 1; boy <= boys; boy++) {         // 遍历男生
        for (int girl = 1; girl <= girls; girl++) { // 遍历女生
            total++;  // 每对算一种
        }
    }
    
    // 第二类:三人组(两个男生 + 一个女生)
    // 需要选两个不同的男生,所以用两层循环,但注意 (boy1,boy2) 和 (boy2,boy1) 算同一组
    // 我们让 boy1 < boy2 来避免重复计数
    for (int boy1 = 1; boy1 <= boys; boy1++) {        // 第一个男生
        for (int boy2 = boy1 + 1; boy2 <= boys; boy2++) { // 第二个男生,编号更大
            for (int girl = 1; girl <= girls; girl++) {   // 选一个女生
                total++;  // 每组三人算一种
            }
        }
    }
    
    cout << "一共有 " << total << " 种选法" << endl;
    // 结果应该是 6 (两人组) + 3*2 = 6 (三人组) = 12
    return 0;
}

运行这个程序,你就会得到12种。注意在枚举三人组时,我们用 boy1 < boy2 避免重复选取同一组男生(因为两个男生不分顺序)。这就是用循环模拟组合的方法。


? 相关知识点指引

  • 加法原理和乘法原理的基本概念:如果还不熟悉,可以先复习基础。
  • 排列与组合:当需要选多个不同的人或物品时,常常要用到组合数公式 C(n,m),这能让你不用循环也能快速算出结果。
  • 容斥原理:当分类之间有重叠时,需要用到容斥原理来避免重复计算。
  • 编程与数数:用循环模拟是验证计数结果的好方法,但要注意循环的写法要正确反映计数规则。

下次遇到复杂的计数问题,记得先问自己:“我该分成几类?每一类里又需要分几步?” 想清楚了,答案就离你不远了。

例题精讲

1单选题

用数字0,1,2,3,4,5可以组成多少个无重复数字且个位是偶数的四位数?

A156
B180
C216
D144
2单选题

用数字0,1,2,3,4可以组成多少个无重复数字且大于3000的四位数?

A48
B36
C24
D60
3判断题

在解决计数问题时,如果一个问题可以分成若干互斥的类别,每类内部又需要分步完成,那么应该先分类再分步,即加法原理后接乘法原理。

4填空题
下面的C++代码用于计算用数字0-5组成无重复数字且能被5整除的三位数的个数。请补全代码。\n\nint count = 0;\n// 个位为0\ncount += 5 * 4;\n// 个位为5\ncount += ___;
5填空题
下面的C++代码用于计算用数字1-9组成无重复数字且百位<十位<个位的三位数的个数。请补全循环内的代码。\n\nint count = 0;\nfor(int i=1;i<=9;i++){\n    int n = 9 - i;\n    count += ___; // 从大于i的数字中选两个递增排列\n}