1 条题解
-
0
P1601. Pizza Anyone? 题解
一、题目分析
本题要求判断是否存在一种披萨配料组合,使得对于每个朋友的所有配料请求,至少满足其中一个。每个请求可以是包含(
+
)或不包含(-
)某个配料,需要找到一个配料集合,使得每个朋友的请求列表中至少有一个条件被满足。关键条件
- 每个朋友的约束条件是一个逻辑“或”(至少满足一个请求)。
- 配料共有16种(A-P),每种配料有两种状态:包含(1)或不包含(0)。
二、解题思路
位运算优化
将每个朋友的请求转换为位掩码,利用位运算快速判断条件是否满足:
- 正请求(+X):表示配料X必须包含,对应位掩码中X的位置为1。
- 负请求(-X):表示配料X必须不包含,对应位掩码中X的位置为0(即全集掩码
Max
异或后该位为1)。
算法步骤
- 输入解析:将每个朋友的请求拆分为正请求和负请求,分别存储为两个位掩码(
person[num][1]
和person[num][0]
)。person[num][1]
:包含的配料位掩码(正请求的按位或)。person[num][0]
:不包含的配料位掩码(负请求的按位或,需通过全集掩码转换为“必须不包含”的条件)。
- 枚举所有可能的配料组合:使用位掩码
status
枚举0到(1<<16)-1
的所有可能(共65536种)。 - 条件判断:对于每个
status
,检查是否满足所有朋友的约束条件:- 对于朋友
num
,若status
包含其正请求的任意一位(status & person[num][1]
),或不包含其负请求的任意一位((status ^ Max) & person[num][0]
),则该朋友的条件被满足。
- 对于朋友
- 输出结果:找到第一个满足条件的
status
,按字母序输出配料;若无满足条件的组合,输出无解信息。
三、代码实现
#include <cstdio> #include <cstring> int main() { char str[50]; // 存储输入行 while (scanf("%s", str) != EOF) { // 循环处理每组数据 int n = 0; // 朋友数量 int person[100][2] = {0}; // person[num][1]存储正请求掩码,person[num][0]存储负请求掩码 int Max = (1 << 16) - 1; // 全集掩码(16位全为1) // 读取约束条件,直到遇到单独的`.` while (str[0] != '.') { for (int i = 0; i < strlen(str) - 1; i += 2) { // 遍历每个请求(每两个字符为一个请求) char sign = str[i]; char topping = str[i + 1]; int bit = 1 << (topping - 'A'); // 计算当前配料对应的位掩码 if (sign == '+') { person[n][1] |= bit; // 正请求:包含该配料,添加到正掩码 } else { person[n][0] |= bit; // 负请求:不包含该配料,添加到负掩码(后续通过异或处理) } } n++; // 朋友数量加1 scanf("%s", str); // 读取下一行 } int status; // 枚举所有可能的配料组合(0到2^16-1) for (status = 0; status <= Max; status++) { int num; for (num = 0; num < n; num++) { // 检查当前组合是否满足第num个朋友的条件: // 正请求至少满足一个(status & person[num][1]不为0) // 或负请求至少满足一个((status ^ Max) & person[num][0]不为0,即status中对应位为0) if (!(status & person[num][1] || (status ^ Max) & person[num][0])) { break; // 不满足,尝试下一个组合 } } if (num == n) { // 所有朋友的条件都满足 break; } } // 输出结果 if (status <= Max) { // 找到有效组合 printf("Toppings: "); for (int i = 0; i <= 15; i++) { // 按字母序输出配料(A-P对应0-15位) if (status & (1 << i)) { printf("%c", 'A' + i); } } printf("\n"); } else { // 无解 printf("No pizza can satisfy these requests.\n"); } } return 0; }
四、代码解析
-
输入处理:
- 逐行读取输入,直到遇到
.
,解析每个朋友的正负请求,分别存储为位掩码。 - 正请求(
+X
)直接按位或到person[num][1]
。 - 负请求(
-X
)按位或到person[num][0]
,后续通过与status ^ Max
(即取反)判断是否不包含该配料。
- 逐行读取输入,直到遇到
-
枚举与判断:
- 使用
status
枚举所有可能的配料组合(0到65535)。 - 对每个
status
,检查是否满足所有朋友的条件:正请求或负请求至少有一个成立。
- 使用
-
结果输出:
- 若找到有效组合,按字母序拼接配料代码并输出。
- 若无解,输出特定提示信息。
五、优化说明
- 位运算高效性:通过位掩码将配料组合和约束条件转换为整数运算,显著提高枚举效率(65536次循环在C语言中可快速完成)。
- 提前终止:一旦发现某个朋友的条件不满足,立即跳出内层循环,减少不必要的计算。
该方法利用位运算的简洁性和高效性,巧妙地将逻辑判断转换为位操作,是解决此类二进制枚举问题的经典思路。
- 1
信息
- ID
- 602
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者