题目来源:组合的输出 - 洛谷
参考书目:《深入浅出程序设计竞赛(基础篇)》
解题思路:通过位运算来枚举数组a
的所有子集,直到找到r的子集,按从小到大输出。解题思路很好理解,问题是怎么用位运算来枚举数值a的所有子集。具体思路如下:
1 、S = (1 << n) - 1:
1 << n
:这是位运算中的左移操作,意味着数字 1 在二进制形式下向左移动 n
位。例如,如果 n = 3
,那么 1 << n
的结果是 1000
(二进制),即 8
(十进制)。
- 1
:从 1 << n
的结果中减去 1。继续上面的例子,8 - 1
等于 7
,在二进制中表示为 111
。
因此,int S = (1 << n) - 1;
实际上是设置 S
为一个二进制数,该数由 n
个 1
组成,代表一个大小为 n
的集合的全集,其中每个 1
可以看作是集合中一个元素的存在。
也就是说第一次for
循环的目的是枚举一个大小为 n
的集合的所有可能的子集,从最大的全集(所有元素都存在)到空集(没有元素)。
2、 检查每个子集的大小
(1)内层循环 for (int i = 0; i < n; i++)
:这个循环遍历 S
的每一位。循环中的 i
代表集合中元素的位置索引。
(2)位运算检查 if (S & (1 << i))
:这里使用了位与运算(&
)来检查 S
中的第 i
位是否为 1
。具体来说,(1 << i)
生成了一个只在第 i
位为 1
的数,其他位都是 0
。当这个数与 S
进行位与运算时,只有当 S
的第 i
位也为 1
时,结果才不为 0
,即 if
条件为真。这表示集合中的第 i
个元素在当前子集 S
中。
3、记录和计数:每当发现第 i
个元素在子集中时(即 if (S & (1 << i))
为真),代码就会将 i
存入数组 a
中,并且计数器 cnt
会增加 1
。数组 a
用来记录当前子集中所有元素的索引,而 cnt
则表示当前子集中元素的数量。
4、检查子集大小:外层循环的每次迭代结束时,通过 if (cnt == r)
检查当前子集的大小是否等于 r
。如果等于,那么就以特定的格式输出该子集中元素的索引(逆序并调整为从 1
开始计数)。
#include<iostream>
#include<cstdio>
using namespace std;
int a[30];
int main()
{
int n, r;
cin >> n >> r;
//从全集枚举到0
for (int S = (1 << n) - 1; S >= 0; S--) { //从n个1一直枚举到n个0
int cnt = 0;
for (int i = 0; i < n; i++)
{
if (S & (1 << i))
a[cnt++] = i;
}
if (cnt == r)//找到子集,逆序输出这个子集的元素
{
for (int i = r - 1; i >= 0; i--)
{
printf("%3d", n - a[i]);
}
puts(" ");
}
}
return 0;
}