奖(老)励(虎)机

题目

游戏厅一批奖(老)励(虎)机,可以无限投币,如何让受益最高?

分析

  1. 受益最高的方案是把所有金币都投给爆率最高的机器;
  2. 但是机器爆率未知,所以就可以蜕化为:快速找到爆率最高的机器,并把剩余金币全部投给这台机器;
  3. 进一步简化为:找到爆率最高的机器。

解决

/*
测试
*/
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
}
posted @ 2024-12-27 10:36  qingchuwudi  阅读(15)  评论(0)    收藏  举报