农心杯三国围棋擂台赛中轮空队的取胜概率

今年的农心杯三国围棋擂台赛进行地如火如荼。到目前为止,中日韩三队每队都只剩下了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;
}

计算结果是,

每队人数轮空队最终获胜概率
150%
243.75%
341.4062%
440.2344%
539.4897%
1137.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]))

(完)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值