搜索入门练习题8 选书 题解
题目出处:《信息学奥赛一本通》例5.7
题目描述
学校放假时,汪老师有A、B、C、D、E五本书,要分给参加培训的张、王、刘、孙、李五位同学,每人只能选一本书。老师实现让每个人将自己喜欢的数填写在如下的表格中。然后根据他们填写的表来分配书本,希望设计一个程序帮助老师求出所有可能的分配方案,使每个学生都满意。
A | B | C | D | E | |
---|---|---|---|---|---|
张同学 | Y | Y | |||
王同学 | Y | Y | Y | ||
刘同学 | Y | Y | |||
孙同学 | Y | ||||
李同学 | Y | Y |
题目分析
可用穷举法(这里说的“穷举”就是“枚举”),先不考虑“每人都满意”这一条件,这样只剩下“每人选一本且只能选一本”这一条件。在这个条件下,可行解就是五本书的所有全排列,一共有 \(5!=120\) 种。然后在 \(120\) 种可行解中一一删去不符合“每人都满意”的解,留下的就是本题的解答。所以仔细观察这道题目其实也是“全排列”的变形题。
我们可以开一个数组 ans[]
用于存放每个人分到的书的编号( \(ans[id]\) 对应第 \(id\) 个人获得的书的编号),不过我们这里给每个人和书的编号都从 \(0\) 开始。
然后我们开一个 void f(int id)
函数用于给第 \(id\) 位同学分配一本书,这样,当我 \(id = 5\) 的时候就说明第 \(0\) 到 \(4\) 位同学已经分配好书了,但是这种分配方案不一定合法,所以我们还需要验证一下这种分配方案是否确保每个同学都被分配到了自己喜欢的书了。如果是的话就输出这种分配方案。
如果当前 \(id \lt 5\) ,说明我还需要从编号从 \(0\) 到 \(4\) 的书中挑选出一本还没有被选过的书然后进行挑选,我们可以再开一个 bool p[]
数组,用 p[i]
来表示第 \(i\) 本书是否已被挑选去了。如果第 \(i\) 本书没有被挑选,则可以将其先分配给第 \(id\) 位同学,同时将 p[id]
设为 true
, 然后递归地往下一层进行搜索,然后等我们搜索回溯回来再将 p[i]
设回 false
即可。
实现代码如下:
#include <bits/stdc++.h>
using namespace std;
bool like[][5] = { // book[i][j]用于表示第i位同学是否喜欢第j本书
0, 0, 1, 1, 0,
1, 1, 0, 0, 1,
0, 1, 1, 0, 0,
0, 0, 0, 1, 0,
0, 1, 0, 0, 1
};
int ans[5]; // ans[id]表示分配给第id位同学的书的编号
bool p[5]; // p[i]为true说明第i本书已被分配;为false说明第i本书没有被分配
// 这里假设同学和书本的编号都从0开始
// check函数用于判断当前的分配方案是否让所有的同学都满意
bool check() {
for (int i = 0; i < 5; i ++)
if (!like[i][ ans[i] ]) // 如果分配给第i位同学的书他不喜欢,返回false
return false;
return true; // 如果没有不喜欢,那么就是都喜欢,返回true
}
// output函数用于输出一种分配方案
void output() {
for (int i = 0; i < 5; i ++)
printf("%5d", ans[i]+1); // 因为坐标从0开始,所以我这里输出的时候多加了一个1
cout << endl;
}
// f(id)用于给第id位同学分配一本没有分配的书
void f(int id) {
if (id == 5) { // 边界条件,说明第0到4位同学都分配了书
if (check()) output(); // 如果分配方案合法,则输出
return;
}
for (int i = 0; i < 5; i ++) { // 遍历第i本书
if (!p[i]) { // 如果第i本书没有被分配,
ans[id] = i; // 则常识性地将其分配给第id位同学
p[i] = true; // 同时标记一下第i本书的分配状态为true(已分配)
f(id+1); // 递归地进下一轮搜索
p[i] = false; // 回溯回来后几个还原第i本书的状态为false(未分配)
}
}
}
int main() {
f(0);
return 0;
}