今年的农心杯三国围棋擂台赛进行地如火如荼。到目前为止,中日韩三队每队都只剩下了2个人。
有人提出,三个队进行擂台赛时,首次轮空的那个队,其胜率有多高呢?假设任意2个棋手对弈,胜率均为50%.
就这个问题,笔者写了一个小程序。算法是用递归做深度优先搜索;数据结构是用3个数字记录每队的剩余人数,另用一个变量记录当前对弈双方;另外,使用备忘录方法记录已计算过的情况,以指数级提升了性能。
具体程序如下:
#include <iostream>
#include <unordered_map>
using namespace std;
enum RaceParty {
AB = 0,
AC,
BC
};
struct Race {
int ACount;
int BCount;
int CCount;
RaceParty raceParty;
Race(int a=0, int b=0, int c=0, RaceParty rp=AB) : \
ACount(a), BCount(b), CCount(c), raceParty(rp){};
bool operator == (const Race& r) const {
return ACount == r.ACount && BCount == r.BCount && CCount == r.CCount && raceParty == r.raceParty;
}
};
namespace std
{
template<>
struct hash<Race>
{
size_t operator() (const Race& r) const noexcept
{
int h = (r.ACount << 24) + (r.BCount << 16) + (r.CCount << 8) + r.raceParty;
return std::hash<int>()(h);
}
};
}
unordered_map<Race, double> cache;
double do_race(struct Race& race)
{
// cout << race.ACount << " " << race.BCount << " " << race.CCount << " " ;
// cout << ( race.raceParty == AB ? "AB" : (race.raceParty == BC ? "BC" : "AC") ) << endl;
if (cache.find(race) != cache.end()) {
return cache[race];
}
RaceParty tmp = AB;
double result = 0;
if (race.ACount > 0 && race.BCount == 0 && race.CCount == 0) {
result = 0;
}
else if (race.ACount == 0 && race.BCount > 0 && race.CCount == 0) {
result = 0;
}
else if (race.ACount == 0 && race.BCount == 0 && race.CCount > 0) {
result = 1;
}
else if (race.ACount > 0 && race.BCount > 0 && race.CCount == 0) {
tmp = race.raceParty;
race.raceParty = AB;
race.ACount --;
result += 0.5 * do_race(race);
race.ACount ++;
race.BCount --;
result += 0.5 * do_race(race);
race.BCount ++;
race.raceParty = tmp;
}
else if (race.ACount > 0 && race.BCount == 0 && race.CCount > 0) {
tmp = race.raceParty;
race.raceParty = AC;
race.ACount --;
result += 0.5 * do_race(race);
race.ACount ++;
race.CCount --;
result += 0.5 * do_race(race);
race.CCount ++;
race.raceParty = tmp;
}
else if (race.ACount == 0 && race.BCount > 0 && race.CCount > 0) {
tmp = race.raceParty;
race.raceParty = BC;
race.BCount --;
result += 0.5 * do_race(race);
race.BCount ++;
race.CCount --;
result += 0.5 * do_race(race);
race.CCount ++;
race.raceParty = tmp;
}
else if (race.ACount > 0 && race.BCount > 0 && race.CCount > 0) {
if (race.raceParty == AB) {
race.ACount --;
race.raceParty = BC;
result += 0.5 * do_race(race);
race.raceParty = AB;
race.ACount ++;
race.BCount --;
race.raceParty = AC;
result += 0.5 * do_race(race);
race.raceParty = AB;
race.BCount ++;
}
else if (race.raceParty == BC) {
race.BCount --;
race.raceParty = AC;
result += 0.5 * do_race(race);
race.raceParty = BC;
race.BCount ++;
race.CCount --;
race.raceParty = AB;
result += 0.5 * do_race(race);
race.raceParty = BC;
race.CCount ++;
}
else if (race.raceParty == AC) {
race.ACount --;
race.raceParty = BC;
result += 0.5 * do_race(race);
race.raceParty = AC;
race.ACount ++;
race.CCount --;
race.raceParty = AB;
result += 0.5 * do_race(race);
race.raceParty = AC;
race.CCount ++;
}
}
else {// all the 3 numbers are zero
cout << "Should never hit here" << endl;
}
cache[race] = result;
return result;
}
int main()
{
int num = 0;
cout << "Please input team member count: ";
cin >> num;
if (num < 1 || num >= 20) {
cerr << "Wrong input. Should be between 1 and 20.\n";
exit(-1);
}
Race race(num, num, num, AB);
double result = do_race(race);
cout << "Probablity of C win is: " << result << endl;
return 0;
}
计算结果是,
每队人数 | 轮空队最终获胜概率 |
---|---|
1 | 50% |
2 | 43.75% |
3 | 41.4062% |
4 | 40.2344% |
5 | 39.4897% |
… | … |
11 | 37.5478% |
注意,此题不可用下面这种方法做: 用3个全局量记录下所有最终结束方式,然后看轮空队最终的结束方式有多少种。
为什么呢?举个例子,假设只有2个队比赛,一个队是1个人,另一个队是2个人,那么2个人的队最终取胜的概率是多少呢?
稍微分析一下就知道,2个人的队最终获胜的概率是75%. 但是,如果用分析最终结束方式的方法,得到的答案是 2/3 = 66.67%, 这是错误的。其实,在这种情况下,最终结束方式共有4种,其中3种都是2人队获胜,因此胜率为75%;但是容易被漏算成最终结束方式只有3种,2种是2人队获胜,因此错误。
后记:
附一篇Python的代码,短小精悍。来自网络。
主要思想是使用变量代替了具体的ABC三队,因而可省略大量的类似代码。
#coding=utf-8
import numpy as np
a = dict() # 备忘录方法
def get(x):
if tuple(x) not in a:
# 如果已经有两个国家没人了,游戏就可以结束了
if x[0] == 0 and x[1] == 0:
return [0, 0, 1]
elif x[0] == 0 and x[2] == 0:
return [0, 1, 0]
elif x[1] == 0 and x[2] == 0:
return [1, 0, 0]
one = x[3] # 对战的一方
two = x[4] # 对战的另一方
three = 3 - one - two # 观战的一方
# 如果人数不够,那就让观战的一方上场
if x[one] == 0:
x[3] = three
return get(x)
if x[two] == 0:
x[4] = three
return get(x)
# 如果two胜利
ne = x[:]
ne[one] -= 1
ans = np.array([0.0, 0.0, 0.0])
ne[3] = two
ne[4] = three
ans += 0.5 * np.array(get(ne))
# 如果one胜利
ne = x[:]
ne[two] -= 1
ne[3] = one
ne[4] = three
ans += 0.5 * np.array(get(ne))
a[tuple(x)] = ans
return a[tuple(x)]
# print(get([5, 5, 5, 0, 1]))
# print(get([2, 2, 2, 0, 1]))
print(get([20, 20, 20, 0, 1]))
(完)