[YNOI2019]游戏
题目
(原题题意不清)有\(n\)个人玩,开始时从\(1\)号到\(n\)号依次排队。每轮游戏有\(4\)人参加,一轮游戏中每位玩家胜率相等,胜者将称为擂主继续游戏,剩下三位将按照入场顺序排在队尾,接下来按队列继续游戏。如果擂主在本轮输了,将排在剩下两名输了的选手的前面。获得最终胜利的条件为连胜\(m\)轮。最后询问第\(k\)个人获得最终胜利的概率。\(4\leqslant n\leqslant10\),\(k\leqslant n\),\(m\leqslant 10\)。
题解
难在状态设计。状态也许依赖于顺序,但仔细想一想可以发现我们只关心第\(k\)个人的位置以及擂主的连胜数(不管他是谁)。于是可以得到状态\(f_{i,j}\)表示第\(k\)个人在从前往后的第\(j\)个位置,当前擂主(站在第一位的)连胜\(i\)场时第\(k\)个人能获得最终胜利的概率。显然\(f_{m,1}=1\),\(f_{m,i}=0(i\ne1)\)。接着我们分类讨论:
第\(k\)个人是擂主,即\(j=1\)
那么守擂成功的概率为\(25\%\),失败的概率为\(75\%\),分别对应到状态\(f_{i+1,1}\)和\(f_{1,n-2}\)(在另外两人之前),即
其他人是擂主,即\(j\ne1\)
稍微复杂了些。分为\(k\)在前四个人中以及\(k\)不在前四个人中。然后还要细分。
当\(j=2,3,4\)时,第\(k\)个人均有\(25\%\)的概率获胜,此时会转移到\(f_{1,1}\),即
对于剩下的\(75\%\),\(j\)一定会跑到队尾,具体在哪里仍然要细分。
当\(j=2\)时,仍然要考虑擂主会不会获胜。仍然有\(25\%\)的概率擂主会获胜,此时第\(k\)个人会到第\(n-2\)个位置,即到达\(f_{i+1,n-2}\);如果擂主失败,那么对于\(k\)获胜的情况上面讨论过,剩下\(50\%\)另外两人获胜,擂主连胜次数变成\(1\),但注意此时\(k\)会跑到\(n-1\)的位置因为擂主要排在他前面,即转移成\(f_{1,n-1}\),即
当\(j=3\)时,同上,如果擂主连胜,有\(25\%\)的概率转移到\(f_{i+1,n-1}\);如果其它两人获胜,要细分是\(k\)前面的人还是后面的人(因为这会影响到队列顺序)。如果前面的人赢了,\(k\)号会排到\(n-1\)位,即\(25\%\)的概率转移到\(f_{1,n-1}\);如果后面的人赢了,\(k\)会排到\(n\)位,即\(25\%\)的概率转移到\(f_{1,n}\),即
当\(j=4\)时,继续分析,得到下面
其余情况下,第\(k\)个人只会前进\(3\)位,分为两种情况
最后根据以上的关系式列方程,gauss消元即可,最终答案为\(f_{0,k}\)。状态集\(\mathcal O(nm)\),复杂度\(\mathcal O(n^3m^3)\)。
听说还有\(\mathcal O(n^2m+n^3)\)的复杂度?这里\(\text{%%%C}\color{red}{\text{razysky}}\)。发现所有转移都是要么往连胜次数\(i+1\)要么往\(1\),构成环的关键是从\(f_{i,j}\)跳到了\(f_{1,j}\)。这里把所有的\(f_{1,x}\)看成未知数,然后一级一级往上列出\(f_{i,j}\)与\(f_{1,x}\)的关系,回到\(f_{1,x}\)的恰好能列出来方程,最后能得到\(\mathcal O(nm)\)个方程,右边是\(f_{1,x}\),左边是\(\sum f_{1,i}\)。把右边相同的合并起来,最后合并成\(n\)个方程,再进行gauss消元,就能做到这个复杂度了。
代码
直接gauss消元的做法。
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
const int maxn = 15;
int n, m, k;
double a[maxn*maxn][maxn*maxn];
void Gauss(int n) {
rep(i, 1, n) {
int r = i;
rep(j, i+1, n) if (fabs(a[j][i]) > fabs(a[r][i])) r = j;
if (r != i) rep(j, 1, n+1) std::swap(a[r][j], a[i][j]);
rep(j, 1, n) if (j != i) {
double t = a[j][i] / a[i][i];
rep(k, 1, n+1) a[j][k] -= t * a[i][k];
}
}
rep(i, 1, n) a[i][n+1] /= a[i][i];
}
#define id(i, j) ((i)*n+(j))
int main() {
for (int T = read(); T; T--) {
n = read(), m = read(), k = read();
memset(a, 0, sizeof a);
rep(i, 0, m)
rep(j, 1, n)
a[id(i, j)][id(i, j)] = 1;
a[id(m, 1)][n*(m+1)+1] = 1;
rep0(i, m) {
a[id(i, 1)][id(i+1, 1)] -= 0.25;
a[id(i, 1)][id(1, n-2)] -= 0.75;
a[id(i, 2)][id(1, 1)] -= 0.25;
a[id(i, 3)][id(1, 1)] -= 0.25;
a[id(i, 4)][id(1, 1)] -= 0.25;
a[id(i, 2)][id(i+1, n-2)] -= 0.25;
a[id(i, 2)][id(1, n-1)] -= 0.5;
a[id(i, 3)][id(i+1, n-1)] -= 0.25;
a[id(i, 3)][id(1, n-1)] -= 0.25;
a[id(i, 3)][id(1, n)] -= 0.25;
a[id(i, 4)][id(i+1, n)] -= 0.25;
a[id(i, 4)][id(1, n)] -= 0.5;
rep(j, 5, n)
a[id(i, j)][id(i+1, j-3)] -= 0.25,
a[id(i, j)][id(1, j-3)] -= 0.75;
}
Gauss(n*(m+1));
printf("%.6lf\n", a[id(0, k)][n*(m+1)+1]);
}
return 0;
}