这是对TopCoder SRM 466 "Lottery Ticket" problem的提交。我已经看到这种模式多次用于此问题。
尼克喜欢玩彩票。单个彩票的成本是价格。尼克正好有四张价值为
b1
,b2
,b3
和b4
的钞票(某些值可能相等)。他想知道是否有可能在没有任何改变的情况下购买单张彩票。换句话说,他想用他的钞票的任何子集支付票的确切价格。返回"可能"如果可能或"不可能"如果不是(为清晰起见,所有引号)。
string buy(int p, int b1, int b2, int b3, int b4) {
int arr[] = {b1, b2, b3, b4};
for (int msk = 0; msk < (1 << 4); ++msk) {
int sum = 0;
for (int i = 0; i < 4; ++i) {
if (msk & (1 << i)) {
sum += arr[i];
}
}
if (sum == p) return "POSSIBLE";
}
return "IMPOSSIBLE";
}
有人可以解释这是如何工作的吗?我不明白为什么他将值放入数组并使用两个嵌套for循环进行循环。
答案 0 :(得分:1)
对于每张纸币,您有两种选择,接受或离开(打开或关闭)。 使用4个音符,您可以将它们视为4位,如果以二进制形式通过0000到1111,则可以选择拾取它们的所有可能组合。
这就是比特矢量的作用。外部循环生成所有可能的子集,内部循环评估子集以查看它是否与所需的总和匹配。
答案 1 :(得分:1)
它只生成所有16种可能的组合。每个组合用4位表示,1表示使用钞票,0表示不使用钞票。
然后计算组合的总和,如果总和正确,则打印“可能”。
答案 2 :(得分:1)
这个问题可以扩展到任意数量的钞票,但让我们来看看这个例子。
这个解决方案的想法是使用强力方法来解决问题。这意味着我将尝试所有可能的解决方案,如果其中一个正在运行,那么结果是积极的。
在这种情况下,工作解决方案意味着我所选择的银行票据总和等于p
。
让我们先看看这段代码:
for (int msk = 0; msk < (1 << 4); ++msk)
这表示我将重复从0
到2^4-1
的所有数字,即0-15
。
如果你用二进制表示法编写这些数字,你会注意到它们涵盖了长度为4的所有可能组合(我们不必写入所有前导零,但它实际上总共有32位用于类型int
)。
0000
0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111
让我们选择其中一个例子,例如: 1010
。这意味着我将在1
和3
位置选择数字(从右到左依次为0)。然后我将检查这两个数字的总和是否等于p
。
下一个for循环对具有1
的位置的所有数字求和:
for (int i = 0; i < 4; ++i) {
if (msk & (1 << i)) {
sum += arr[i];
}
}
如果我们分解它,那么我们有msk
代表我们正在检查的当前组合,而(1 << i)
只是向左移位按位操作,它给我们2^i
,或者用二进制表示法:
0001 = 1 << 0
0010 = 1 << 1
0100 = 1 << 2
1000 = 1 << 3
注意:(1 << i)
位于括号内,因为&
具有更高的优先级,在这种情况下我们不希望这样。
如果在两个整数之间使用&
运算符,则会得到按位运算,例如
1010 & 1000 = 1000 // this is greater than 0
1010 & 0100 = 0000 // this is equal to 0
因此if (msk & (1 << i))
仅适用于当前组合中1
的位置,即msk
。
我希望这也解释了为什么他将值放在数组中的原因 - 它是因为他想为每个钞票分配一个索引,然后如果掩码有1
则使用该注释位置,而不是弄清楚应该使用哪四个变量。