题目
Description
In the Pass the Buck game, there are several players and each player has one or more neighbors among the other players.
Each game begins with one player chosen at random who receives a buck (the current holder). (For this game, chosen at random means each possible outcome has the same probability.)
Then, at each step, if the current holder has d neighbors, he/she chooses an integer k in the range [0, d] at random. If 0 is chosen, the current holder is the winner and keeps the buck. Otherwise, the holder passes the buck to the neighbor with index k, who becomes the new holder.
The game continues until some holder wins.
Write a program to find the probability that player j wins if player k is the first holder.
The configuration of players and neighbors can be modeled as a graph with the players as vertices and an edge between two vertices if the corresponding players are neighbors. For example, if they are seated in a row then the neighbors are the players to the left and right (if any).
If they are seated around a table, then each player has a neighbor to the right and left.
If they are seated in multiple rows, they may have neighbors left, right, in front and/or behind.
Format
Input
The input consists of multiple lines of input. The first line contains the number, N, (2 <= N <= 20) of players followed by a space followed by the number, P, (1 <= P <= 20) of start/winner pairs.
The next N lines of input give the neighbors of each player. Line m gives the neighbors of player m. The first integer on the line is the number, ***d(m)***, of neighbors of player m. This is followed by d(m) integers giving the indices of the neighbors of m, separated by spaces.
The next P lines of input give the index, s, of the start holder and the index, w, of the player whose probability of winning when s is the first holder is to be found. Each line contains three integers, j, s(j) and w(j) separated by spaces where j is the index of the pair.
Output
Your program will generate P lines of output.
The
j
t
h
j^{th}
jth line contains the integer j a space and a 5 decimal point value which is the probability that player w(j) wins if player s(j) is the first holder.
Samples
Input | Output |
---|---|
5 4 1 2 2 1 3 2 2 4 2 3 5 1 4 1 1 1 2 2 2 3 3 3 4 4 4 | 1 0.61818 2 0.47273 3 0.45455 4 0.47273 |
思路
分析题意,似乎题目所求解的获胜情况并不是有限的。例如:在图一中,当发牌给1号时,若求解1号获胜的概率,则可以考虑以这些情况:1→1(结束),1→2→1→1(结束),1→2→3→2→1→1(结束),1→2→3→4→3→2→1→1(结束),1→2→3→4→5→4→3→2→1→1(结束)。这些只是典型的几种,但诸如1→2→1→2→1→2→1→2→1→2→1→2→1→2→1→2→1→2→1→2→1→1(结束)的类似情况也不乏可能性(即使可能性相当小)。
简单dfs方案
于是有了第一种思路:dfs。对于给定的起点start
和终点end
以及行至该起点的概率,找到起点所有的邻接点,平均分配概率,再逐一将邻接点坐标赋值给start
,以此方式进行递归运算。为减小误差,当某条路径的基准概率低于1e-13(实验得到)时停止搜索。代码如下:
double ProbabilityWin(int start, int end, double presetprob) {
double ret = 0.0;
if (presetprob >= 0.0000000000001) {
int i;
if (start == end) ret += presetprob / (double)(degree[start] + 1);
for (i = 1; i <= N; i++) {
if (graph[start][i])
ret += ProbabilityWin(i, end, presetprob / (double)(degree[start] + 1));
}
}
return ret;
}
其中graph
为图的邻接矩阵,degree
记录了各节点的度。调用时设定presetprob = 1.0
。
显而易见,结局是悲剧的。用题目中给的样例打表计时:
The algorithm took 1046.88ms to solve the problem
然后试试几组更大的数据:
The algorithm took 8640.62ms to solve the problem
更大的数据…抱歉,三十分钟过去了…
基于bfs优化后的算法
接着分析过程,大致如下图所示:
上图描述了题述第一种情况中前五轮模拟过程。我们以此方式进行1号的胜率计算。注意到在第III轮至第IV轮的计算中,出现了部分元素(即2)的重复计算,而在第IV轮至第V轮的计算中,只需进行三类运算(1,3,5)的情况下我们却计算了六次(1,3,1,3,3,5)。
因此考虑进行概率合并。合并后流程图如下:
数学解释
下面使用数学语言描述这一过程:记节点 V V V 的度为 d ( V ) d(V) d(V)。定义二元有序对
N = ( V , p V ) N=(V,pV) N=(V,pV)
其中 V V V 为节点, p V pV pV 为我们预先定义该节点的的初始概率。
定义任意二元有序对的函数
f ( N ) = { ( V ′ , p V ′ ) ∣ V ′ 为 V 的 所 有 邻 接 节 点 , p V ′ = p V d ( V ) + 1 } f(N) = \lbrace (V', pV') | V'为V的所有邻接节点, pV'=\frac{pV}{d(V) + 1} \rbrace f(N)={(V′,pV′)∣V′为V的所有邻接节点,pV′=d(V)+1pV}
先给出周游前的集合 N S ( 0 ) = { ( S t a r t , p S t a r t = 1 ) } NS(0) = \lbrace (Start, pStart = 1) \rbrace NS(0)={(Start,pStart=1)} 。接着在每一部中依此方式求得 N S ( n ) NS(n) NS(n),即:
N S ( n ) = ⋃ f ( K ) , K ∈ N S ( n − 1 ) NS(n) = \bigcup f(K), K \in NS(n - 1) NS(n)=⋃f(K),K∈NS(n−1)
其中,每次运算结束后,合并 N S ( n ) NS(n) NS(n) 中所有节点相同的有序对,方法如下:
⋃ { ( V , p V i ) } = { ( V , ∑ p V i ) } \bigcup \lbrace (V, pV_i) \rbrace = \lbrace (V, \sum {pV_i}) \rbrace ⋃{(V,pVi)}={(V,∑pVi)}
特殊地,若在某周游步骤中 N S ( n − 1 ) NS(n-1) NS(n−1) 包含一节点为终点的有序对 ( E n d , p E n d ) (End, pEnd) (End,pEnd) ,则在总胜率中加上 p E n d d ( E n d ) + 1 \frac{pEnd}{d(End)+1} d(End)+1pEnd 以统计总胜率。在某次递增的胜率小于1e-7(实验得到)时终止计算,输出计算结果。
代码如下:
double ProbabilityWin(int start, int end) {
double ret = 0.0, curp[21], nextp[21], roundp;
int i, j;
for (i = 0; i < 21; i++) curp[i] = nextp[i] = 0.0;
roundp = 0.0;
curp[start] = 1.0;
do {
ret += roundp;
roundp = 0.0;
for (i = 0; i <= N; i++) nextp[i] = 0.0;
for (i = 1; i <= N; i++)
if (curp[i] > 0.0)
for (j = 1; j <= N; j++)
if (graph[i][j]) {
if (i == j) {
if (i == end) roundp += curp[i] / (double)degree[i];
}
else nextp[j] += curp[i] / (double)degree[i];
}
for (i = 0; i < 21; i++) curp[i] = nextp[i];
} while (roundp == 0.0 || roundp > 0.0000001);
return ret;
}
该实现中使用两数组prob
和nextp
来表示当前一轮周游以及下一轮周游的
N
S
NS
NS 集合,roundp
为当前一轮周游得到的胜率。
以上算法成功将几组数据运算时间压缩到了1ms。
代码
#include "iostream"
#include "cstdio"
using namespace std;
bool graph[21][21] = { false };
int degree[21] = { 0 };
int N, P;
double ProbabilityWin(int start, int end) {
double ret = 0.0, curp[21], nextp[21], roundp;
int i, j;
for (i = 0; i < 21; i++) curp[i] = nextp[i] = 0.0;
roundp = 0.0;
curp[start] = 1.0;
do {
ret += roundp;
roundp = 0.0;
for (i = 0; i <= N; i++) nextp[i] = 0.0;
for (i = 1; i <= N; i++)
if (curp[i] > 0.0)
for (j = 1; j <= N; j++)
if (graph[i][j]) {
if (i == j) {
if (i == end) roundp += curp[i] / (double)degree[i];
}
else nextp[j] += curp[i] / (double)degree[i];
}
for (i = 0; i < 21; i++) curp[i] = nextp[i];
} while (roundp == 0.0 || roundp > 0.0000001);
return ret;
}
int main() {
int i, j, k, t, index, start, end;
cin >> N >> P;
for (i = 1; i <= N; i++) {
cin >> j;
for (k = 0; k < j; k++) {
cin >> t;
graph[i][t] = graph[t][i] = true;
}
graph[i][i] = true;
}
for (i = 1; i <= N; i++) {
for (j = 1; j <= N; j++) {
if (graph[i][j]) degree[i]++;
}
}
for (i = 1; i <= P; i++) {
cin >> index >> start >> end;
printf("%d %.5lf\n", index, ProbabilityWin(start, end));
}
return 0;
}