题目的链接放在文末,大家可以试试再来看本文。
简述一下题意,就是有n个人,站成一个圈。每个人开枪击中别人的概率是固定的(设数组P[i]表示第i个人的命中率),从第一个人开始轮流开枪,求每个人生还的概率是多少?(假设每人都以最有利自己的方式选择射击对象,并且当有多个目标都一样好时,随机选择其中一个。并且,每轮都必须射击,虽然故意放弃射击机会可能增大存活的概率)。
一个引导我们想到方法的条件是,n <= 13且时限是4s,这提示我们这道题不像是个推数学公式的题目,并且很有可能是状态空间递推相关的方法。说到状态空间,必然就要设计状态的表示。直觉告诉我,状态表示是带集合的(因为n太小了),因此设计状态F[a][b][S],表示当S集合的人一起玩这个游戏,并且下一个开枪的人是b时,a最终胜出的概率。
再来研究状态转移。如果a == b,那么显然b不会射击a(不允许自杀=.=),于是选择一个射杀后最大概率使a存活的人,设为c,于是有T = S ^ ( 1 << c )。再计算到c死后下一个射击的player为d,于是转移到F[a][d][T],由此可得hit(a,b,S) = P[b] * F[a][d][T];
另外,如果a != b,情况就不容乐观了。因为b为了使自己生存的几率更大,是可能选择射杀a的。假设现在有m个b中意的(何为中意?即射杀后b生还的概率最大)射杀对象(a是否是一员不用特设处理),对于对象k,设射杀后下一个射击的人是kk,集合变为T(k) = S ^ ( 1 << k ),于是转移到F[a][kk][T(k)]。最终有公式hit(a,b,S) = sum( F[a][kk][T[k]], for all k ) / m。
更有趣的是,如果b没有击中他想要击毙的人,怎么办呢?此时,状态集合S没有变,改轮到下一个人c射击了,于是转移到F[a][c][S],有miss(a,b,S) = ( 1 – P[b] ) * F[a][c][S]。再进一步,如果S中的人射击完这一轮后,都没有命中,是不是又该轮到b射击了,也就是说,这里状态图出现了循环依赖,因此不能直接DP。
要解决循环依赖,其实也不难,假设现在集合中S有三个人a,b,c,从a开始射击,把方程列出来看看:
F[a][a][S] = hit(a,a,S) + ( 1 – P[a] ) * F[a][b][S]
F[a][b][S] = hit(a,b,S) + (1 – P[b] ) * F[a][c][S]
F[a][c][S] = hit(a,c,S) + ( 1 – P[c] ) * F[a][a][S]
移项再整理一下,得:
hit(a,a,S) = F[a][a][S] – ( 1 – P[a] ) * F[a][b][S]
hit(a,b,S) = F[a][b][S] – (1 – P[b] ) * F[a][c][S]
hit(a,c,S) = F[a][c][S] – ( 1 – P[c] ) * F[a][a][S]
这便是一个标准的常系数非齐次线性方程组,用高斯消元解决之。可以证明这个系数矩阵是可逆的,因此它有唯一解。还有一个小优化(其实狠重要的优化)是这个系数矩阵是比较特殊的,据此可以把GE跑到O(n)。
对于每个人x,最后的答案就是F[x][0][(1 << n) – 1 ]。
其实,写此文,我还有一个目的,就是我自知这个方法不是很优秀,特别是上述对于循环依赖的处理,疑有数学方法,或者,还有其它更好的状态表示就能自身解决这个问题,所以希望大家回复赐教,不胜感激。
