今天晚上,有个同学出了道海盗分金币的问题来拷问我。我干脆用程序来把它做了。唉,好吧,承认概率已经退步了,而且还很不爱动脑筋。不过如果让计算机来处理这样的问题,不考虑算法复杂度的话,实在忒简单了。不考虑算法复杂度,几分钟就把程序写完跑通了。可是更高效的程序,和直接的概率解法是怎样的呢?下次请教这位大牛同学吧。
不废话了,先给题:3名海盗分15枚金币和15枚银币,每人最多只能拿12枚钱币,而且必须分完,问总共有多少种不同的分法?
还是不废话了,直接上代码(再次声明,不考虑算法复杂度啊!)
#include <iostream>
struct Pirate {
int gold;
int silver;
};
int main()
{
Pirate a, b, c;
int gold_num = 15, silver_num = 15;
int counter = 0;
for (int i = gold_num; i >= 0; i--) {
for (int j = silver_num; j >= 0; j--) {
a.gold = i;
a.silver = j;
if (a.gold + a.silver > 12 || a.gold + a.silver == 0) continue;
for (int k = gold_num-i; k >= 0; k--) {
for (int l = silver_num-j; l >= 0; l--)
{
b.gold = k;
b.silver = l;
if (b.gold + b.silver > 12 || b.gold + b.silver == 0) continue;
c.gold = 15 - i - k;
c.silver = 15 - j - l;
if (c.gold + c.silver > 12 || c.gold + c.silver == 0) continue;
counter ++;
std::cout << "a.gold=" << a.gold << ", a.silver=" << a.silver << ",b.gold=" << b.gold << ", b.silver=" << b.silver << ",c.gold=" << c.gold << ", c.silver=" << c.silver << std::endl;
}
}
}
}
std::cout << "counter = " << counter << std::endl;
return 0;
}
最后的答案是2422.
换个Python版的程序:
def main():
gold_num, silver_num = 15, 15
limit = 12
counter = 0
for i in range(gold_num, -1, -1):
for j in range(silver_num, -1, -1):
if i+j > limit or i+j == 0:
continue
for m in range(gold_num-i, -1, -1):
for n in range(silver_num-j, -1, -1):
if m+n > limit or i+j == 0:
continue
c1, c2 = gold_num-i-m, silver_num-j-n
if c1+c2 > limit or c1+c2 == 0:
continue
counter += 1
print "a.gold=%d, a.silver=%d, " \
"b.gold=%d, b.silver=%d, " \
"c.gold=%d, c.silver=%d" \
% (i, j, m, n, c1, c2)
print "Counter = %d" % counter
if __name__ == "__main__":
main()
最后补充一下,后来和金融大牛同学联系过后,发现人家是也是写了个程序做的。
如果您有更好的解法,不妨留言。
【后记】2016-06-19更新
鉴于4重for循环的程序较大束缚了因题目变化而引起的程序不变性,比如海盗变为10个,钱币变为10种,因此,本程序其实可改用递归实现。递归的实现可以较好地适应海盗数目的变化,依然不便于适应钱币种类的变化。那么算法复杂度有变化吗?没有。递归的算法复杂度和上述4重for循环的算法复杂度是一样的。除了理论分析,还可以通过添加一个loop_counter变量来统计最内部循环体被执行的次数来验证。
#include <iostream>
#include <sstream>
struct Pirate {
int gold;
int silver;
};
const int limit = 12;
const int totalGold = 15;
const int totalSiver = 15;
const int totalPirates = 3;
Pirate * pList = new Pirate[totalPirates];
int globalIndex = 0;
int Resolve(int leftPirates, int leftGold, int leftSilver)
{
if (leftPirates < 1) return 0;
if (leftPirates == 1) {
if (leftGold + leftSilver > limit || leftGold + leftSilver == 0) {
return 0;
} else {
int index = totalPirates - leftPirates;
pList[index].gold = leftGold;
pList[index].silver = leftSilver;
++globalIndex;
std::ostringstream strm;
strm << globalIndex << ": ";
for (int count = 0; count < totalPirates; ++count) {
strm << "<" << count+1 << ">" << pList[count].gold << ", " << pList[count].silver << " ";
}
strm << "\n";
std::cout << strm.str();
return 1;
}
}
int sum = 0;
for (int i = 0; i <= leftGold; ++i)
for (int j = 0; j <= leftSilver; ++j) {
if (i + j == 0 || i + j > limit) continue; // loop_counter can be added before this line
int index = totalPirates - leftPirates;
pList[index].gold = i;
pList[index].silver = j;
sum += Resolve(leftPirates - 1, leftGold - i, leftSilver - j);
}
return sum;
}
int main()
{
int sum = Resolve(totalPirates, totalGold, totalSiver);
std::cout << "sum = " << sum << std::endl;
delete [] pList;
}
最后,分享一点有趣的数据:
如果改成金币20枚银币20枚,5个海盗,每个最多取16枚,最少取1枚,那么:
总共分法为:67,695,246种,循环体执行次数为:85,254,578次,计算机耗时约:1.2秒。
如果改成金币20枚银币20枚,6个海盗,每个最多取16枚,最少取1枚,那么:
总共分法为:1,847,553,264种,循环体执行次数为:2,196,020,339次,计算机耗时约:28秒。
(完)