我們處理遊戲Boss掉落時經常碰到一個問題,假設這個BOSS會掉3個部位的裝備,
武器:20% 衣服:30% 頭盔:50%
那麼求,期望次數多少,可以集齊這三件裝備
作為程式設計師,我們先來一段暴力破解,迴圈1000000萬次,也便於我們驗證解果
math.randomseed(tostring(os.time()):reverse():sub(1, 6)) local counts = {} local p1, p2, p3 = 0.2, 0.3, 0.5 for j=1, 10000 do local a1, a2, a3 = 0, 0, 0 for i=1,1000000 do local r = math.random() if r > p3 then a3 = a3 + 1 elseif r > p2 then a2 = a2 + 1 else --elseif r > p1 then a1 = a1 + 1 end if a1 > 0 and a2 > 0 and a3 > 0 then table.insert(counts, i) break end end end print(avg_count/#counts) --列印結果,當然你不一定是這個值:6.6702
但是這出來也只是個近似值,得到一個靠譜值,這個時候容斥原理就出來了,以下解釋得真好:
如果被計數的事物有A、B、C三類,那麼,A類和B類和C類元素個數總和= A類元素個數+ B類元素個數+C類元素個數—既是A類又是B類的元素個數—既是A類又是C類的元素個數—既是B類又是C類的元素個數+既是A類又是B類而且是C類的元素個數。(A∪B∪C = A+B+C - A∩B - B∩C - C∩A + A∩B∩C)
再放一下圖:
所以:演算法變成這樣:
print(1/p1+1/p2+1/p3 - 1/(p1+p2) - 1/(p2+p3) - 1/(p1+p3) + 1/(p1+p2+p3))
那如果部位多,那也真夠累的,寫個通用演算法:
--傳入陣列,取裡面的幾個元素進行組合 local function combine(arr, n, m, b, result) n = n or #arr --print(' combine', n, m, arr[n]) assert(n >= m) for i=m, n, 1 do b[m] = i if m > 1 then combine(arr, i - 1, m - 1, b, result) else --用於列印 local s = {} table.foreach(b,function(_,v) table.insert(s,arr[v]) end) table.insert(result, s) end end end --combine({'a','b','c', 'd'},nil,2,{}) --將陣列相加 local function add(arr) assert(type(arr)=='table') local r = 0 for i,v in pairs(arr) do r = r + v end return r end --容斥原理 function InclusionExclusion (ppp) local result = 0 for i,v in ipairs(ppp) do result = result + 1/v end local count = #ppp for i=2,count - 1 do local c = {} combine(ppp, nil, i, {}, c) --遍歷所有的組合 table.foreach(c, function(_,v) --計算組合裡面所有的數 result = result - 1/add(v) end) end result = result + 1/add(ppp) return result end print(InclusionExclusion({p1,p2,p3}) --列印值:6.6547619047619