奖(老)励(虎)机
题目
游戏厅一批奖(老)励(虎)机,可以无限投币,如何让受益最高?
分析
- 受益最高的方案是把所有金币都投给爆率最高的机器;
- 但是机器爆率未知,所以就可以蜕化为:快速找到爆率最高的机器,并把剩余金币全部投给这台机器;
- 进一步简化为:找到爆率最高的机器。
解决
/*
测试
*/
package slotmachine
import (
"math/rand"
"testing"
)
func TestWitchSlotMachine(t *testing.T) {
count := 10
probs := make([]int, count)
maxProb := 0
maxProbIndex := 0
for i := 0; i < count; i++ {
probs[i] = rand.Intn(100)
if probs[i] == 0 {
probs[i] = 1 // 概率不能为0
}
if probs[i] > maxProb {
maxProb = probs[i]
maxProbIndex = i
}
}
t.Logf("slot machine probs: %v\r\n", probs)
t.Logf("slot machine expected: index=%d, probs=%d\r\n", maxProbIndex, maxProb)
result := WitchSlotMachine(count, probs)
t.Logf("slot machine result: index=%d, probs=%d\n", result, probs[result])
}
/*
基本思路:
1. 每一轮投币都会产生一组奖励结果
1.1. 本轮奖励 **产出比率** 少的,下一轮减少投币数量,数量最少为0,标识下一轮不再投币
1.2. 本轮奖励 **产出比率** 多的,下一轮增加投币数量
2. 重复步骤 1 的操作,直到剩下最后一台奖(老)励(虎)机
3. 步骤2的结果即为所求
*/
package slotmachine
import (
"math"
"math/rand"
"sort"
"time"
)
var r = rand.New(rand.NewSource(time.Now().UnixNano()))
// MachineMeta 奖(老)励(虎)机基础数据
type MachineMeta struct {
Index int // 机器所在位置,一旦初始化完成就不可变
BaseCount int // 每次投币数量,用来放大概率的影响
Prob int // 当前机器的中奖概率:0~100
Reward int // 本轮筛选过程中的中奖结果
}
// WitchSlotMachine
// count : 奖(老)励(虎)机数量
// prob : 所有奖(老)励(虎)机爆率,1~100
//
// 返回最终投币的奖(老)励(虎)机
func WitchSlotMachine(count int, probs []int) int {
var (
rangeTimes = 0
n = 100 // 每次投币数量,投币数量越多,误差越小,随之收敛速度越慢
moveN = 10 // 每次最多移动10个金币,数值越大,收敛速度越快(误差也越大) 0 < moveN <= n
)
machineList := initMachineListWithProb(count, n, probs)
deleteTag := make([]int, 0)
for count > 1 {
machineList = StrategyFilter(count, machineList, moveN)
if len(machineList) < count {
deleteTag = append(deleteTag, count-len(machineList))
}
count = len(machineList)
rangeTimes++
}
return machineList[0].Index
}
func initMachineListWithProb(count, n int, prob []int) []*MachineMeta {
machineList := make([]*MachineMeta, count)
for i := range count {
machineList[i] = &MachineMeta{
Index: i,
BaseCount: n,
Prob: prob[i],
}
}
return machineList
}
// StrategyFilter 按既定策略筛选奖(老)励(虎)机
func StrategyFilter(count int, machineList []*MachineMeta, moveN int) []*MachineMeta {
// 投币,并记录本轮奖励情况
for index := range machineList {
machineList[index].Reward = calculateReward(machineList[index].Prob, machineList[index].BaseCount)
}
// 按照本轮奖励升序排列
sort.SliceStable(machineList, func(i, j int) bool {
return machineList[i].Reward > machineList[j].Reward
})
toBeDeleted := make(map[int]struct{}, 0)
i, j := 0, count-1
for i < j {
// 由于已经根据奖励排序,这里的算法逻辑是:
// 1. 本轮产出奖励最小的机器,下一轮投币数量减少moveN
// 2. 本轮产出奖励最多的机器,下一轮投币数量增加moveN
// 3. 从两端往中间收拢,产出奖励相差越小,投币数量变动越少
diff := rewardMove(count, i, moveN)
if machineList[j].BaseCount == 0 {
toBeDeleted[machineList[j].Index] = struct{}{}
} else if machineList[j].BaseCount > diff {
machineList[j].BaseCount -= diff
machineList[i].BaseCount += diff
} else {
machineList[i].BaseCount += machineList[j].BaseCount
machineList[j].BaseCount = 0
toBeDeleted[machineList[j].Index] = struct{}{}
}
i++
j--
}
machineList = deleteMachine(machineList, toBeDeleted)
return machineList
}
// calculateReward 根据概率和投币数量计算:某台奖(老)励(虎)机本轮获取到的奖励
func calculateReward(prob, baseCount int) (reward int) {
probTotal := 0
// 投币 baseCount 次
for _ = range baseCount {
probTotal += r.Intn(prob)
}
// 本轮投币产出的奖励
reward = probTotal / baseCount
return reward
}
// rewardMove: 金币迁移,根据位置计算奖(老)励(虎)机下一次投币:需要增加或减少的金币数量
//
// index 越大,迁移的金币越少(调用前的数据已经经过排序)
func rewardMove(count, index, moveN int) int {
return int(math.Ceil(float64(moveN) * (1 - (float64(index) / float64(count)))))
}
// deleteMachine 移除下次不需要投币的奖(老)励(虎)机
func deleteMachine(machineList []*MachineMeta, toBeDeleted map[int]struct{}) []*MachineMeta {
lastMachineList := make([]*MachineMeta, 0)
for _, pay := range machineList {
if _, ok := toBeDeleted[pay.Index]; !ok {
lastMachineList = append(lastMachineList, pay)
}
}
sort.SliceStable(lastMachineList, func(i, j int) bool {
return lastMachineList[i].Index < lastMachineList[j].Index
})
return lastMachineList
}

浙公网安备 33010602011771号