返回二级目录——kaugnbin概率dp习题集
原题
'Snakes and Ladders' or 'Shap-Ludu' is a game commonly played in Bangladesh. The game is so common that it would be tough to find a person who hasn't played it. But those who haven't played it (unlucky of course!) the rules are as follows.
1. There is a 10 x 10 board containing some cells numbered from 1 to 100.
2. You start at position 1.
3. Each time you throw a perfect dice containing numbers 1 to 6.
4. There are some snakes and some ladders in the board. Ladders will take you up from one cell to another. Snakes will take you down.
5. If you reach a cell that contains the bottom part of a ladder, you will immediately move to the cell which contains the upper side of that ladder. Similarly if you reach a cell that has a snake-head you immediately go down to the cell where the tail of that snake ends.
6. The board is designed so that from any cell you can jump at most once. (For example there is a snake from 62 to 19, assume that another is from 19 to 2. So, if you reach 62, you will first jump to 19, you will jump to 2. These kinds of cases will not be given)
7. There is no snake head in the 100-th cell and no ladder (bottom part) in the first cell.
8. If you reach cell 100, the game ends. But if you have to go outside the board in any time your move will be lost. That means you will not take that move and you have to throw the dice again.
Now given a board, you have to find the expected number of times you need to throw the dice to win the game. The cases will be given such that a result will be found.
Input
Input starts with an integer T (≤ 105), denoting the number of test cases.
The first line of a case is a blank line. The next line gives you an integer n denoting the number of snakes and ladders. Each of the next n lines contain two integers a and b (1 ≤ a, b ≤ 100, a ≠ b). If a < b, it means that there is a ladder which takes you from a to b. If a > b, it means that there is a snake which takes you from a to b. Assume that the given board follows the above restrictions.
Output
For each case of input, print the case number and the expected number of times you need to throw the dice. Errors less than 10-6 will be ignored.
Sample Input
2
14
4 42
9 30
16 8
14 77
32 12
37 58
47 26
48 73
62 19
70 89
71 67
80 98
87 24
96 76
0
Sample Output
Case 1: 31.54880806
Case 2: 33.0476190476
题目大意
题目可看成一个1*100的条形格子,现在你站在第1个格子,格子中有许多传送点(起末点均在1到100之内),有些传送点可以向前传送,有些可以向后传送。但是传送保证不会传送到自己,传送目标点也不会处于另一个传送的起始点,st.不会连续传送。每次投掷一个均匀的骰子(1~6),骰子投出的点数就是向前走的步数,如果目标点大于100则重新投掷,求走到100格子所需投掷骰子次数的期望。(题目保证有解)
思路
设ei代表从第i个格子走到第100个格子所需期望数,显然e100=0,所以从后往前推。
对于普通的点(没有传送门的点),参照常规求解方法列状态转移方程:
e[i]=e[i+1]/6+e[i+2]/6+....+e[i+6]/6 + 1 (当然要另外考虑100-i<6的特殊情况,这里推导通式暂不考虑)
常数移到一边,转化为AX=B的形式:
ei - ei+1/6 - ei+2/6 -... -ei+6/6 =1
而对于向前传送的格子,假设起点为a,终点为a+x,有
e[a]=e[b] ,无后效性
对于向前传送的格子,假设起点为a,终点为a-x,有
e[a]=e[b] ,不满足无后效性,要e[a],需要知道e[a-x],所以不能递推求解
向这种存在环的问题,我们考虑用伟大的方程求解。而这里变量较多,就变成了方程组,考虑用高斯消元法来做。
然后根据以上三个公式,可列100*100系数矩阵,构造增广矩阵,高斯消元后就可以求解出e1,解毕。
PS:学过《数值分析》的童鞋知道规避误差的一个原则:防止分母过小
对于高斯消元法,有一步是需要整行除以主对角线元素的,如果主对角线元素过小则误差会很大。解决方案是将右下矩阵中最大的元素通过行交换和列交换来换掉主对角线元素(当然变量顺序也跟着列交换而改变)。
遗憾的是,出题人显然没学过《数值分析》,我用这个降低误差的方案交上去反而是错的,wa了我一晚上,睡前躺床上突然想到是不是标程误差太大了......第二天删掉降低误差的步骤就过了。好气哦......
AC代码
#include<bits/stdc++.h>
#include<cstring>
#define regi register int
using namespace std;
//https://vjudge.net/problem/LightOJ-1151
typedef double dtype;
struct matRes{
int i;
dtype x;
};
inline int operator<(matRes a,matRes b){
return a.i<b.i;
}
dtype const eps=0;class mat{
#define accuracy 0 //1 是高精度 0是低精度 本题低精度可过不要用高精度,出题者不知道高斯消元降低误差的准则。
#define maxsize 110
dtype m[maxsize][maxsize];
matRes r[maxsize]; //高斯倒换临时数组
int sw[maxsize];
dtype res[maxsize];
int rank;
public:
int w,h;
inline dtype *operator[](int x){
return m[x];
}
inline void clear(){
memset(m,0,sizeof(m));
}
inline void set_rank(int r){//方阵 + [b]
h=r;
w=r+1;
for(regi i=1;i<=w;i++)
sw[i]=i;
}
mat(){
//用的时候要首先规定大小set size或者rank
}
mat(int hh,int ww,dtype *a){
regi i,j;
w=ww;
h=hh;
for(i=1;i<=h;i++)
for(j=1;j<=w;j++)
m[i][j]=*(a+(i*w)+j);
for(i=1;i<=w;i++)
sw[i-1]=i-1;
}
inline void row_times(int r,dtype t){
for(regi i=1;i<=w;i++)
m[r][i]*=t;
}
inline void add_row(int r1,dtype t1,int r2){
dtype t;
for(regi i=1;i<=w;i++)
{
t=m[r1][i]*t1+m[r2][i];
m[r2][i]=t;
}
}
inline void swap_row(int r1,int r2){
for(regi i=1;i<=w;i++)
swap(m[r1][i],m[r2][i]);
}
inline void swap_col(int c1,int c2){
for(regi i=1;i<=h;i++){
swap(m[i][c1],m[i][c2]);
swap(sw[c1],sw[c2]);
}
}
inline void print_debug()//调试用
{
#ifndef ONLINE_JUDGE
cout<<"变量顺序为:"<<endl;
for(int i=1;i<w;i++)
cout<<sw[i]<<' ';
cout<<endl;
cout<<"矩阵为:"<<endl;
for(int i=1;i<=h;i++)
{
for(int j=1;j<=w;j++)
{
if(fabs(m[i][j])<eps)
m[i][j]=0;
cout<<m[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
#endif
}
inline void print_res(dtype dp[],int base=0){
for(regi i=1;i<w;i++)
dp[sw[i]+base]=res[i];
dp[w]=dp[base];
}void Gaussqzy(){
rank=0;
int maxi,maxj;
dtype maxv=0;
for(regi i=1;i<h;i++){
maxv=0;
for(regi j=i;j<=h;j++){
#if accuracy == 1 //高精度
for(regi k=i;k<w;k++){
if(fabs(maxv)<fabs(m[j][k])){
maxv=m[j][k];
maxi=j;
maxj=k;
}
}
#else
if(maxv<fabs(m[j][i])){
maxv=fabs(m[j][i]);
maxi=j;
}
#endif
}
if(fabs(maxv)<eps)
return;
rank++;
if(maxi!=i)
swap_row(i,maxi);
#if accuracy == 1 //高精度
if(maxj!=i)
swap_col(i,maxj);
#endif
row_times(i,1/m[i][i]);
for(regi j=i+1;j<h;j++)
add_row(i,-1*m[j][i],j);
}
if(fabs(m[h][h])>eps)
rank++;
return;
}
inline bool find_res(){
Gaussqzy();
if(rank<h)
return 0;//不满秩
for(regi i=1;i<w;i++){
res[w-1-i]=m[h-i][w];
for(regi j=1;j<i;j++)
res[w-1-i]-=res[w-1-j]*m[h-i][w-1-j];
}
for(regi i=1;i<w;i++){
r[i].i=sw[i];
r[i].x=res[i];
}
sort(r+1,r+w);
for(regi i=1;i<w;i++){
sw[i]=r[i].i;
res[i]=r[i].x;
}
return 1;
}
};dtype dp[1000];
struct inp{
int l,r;
};
inp co[1000];
bool operator<(inp a,inp b){
return a.l<b.l;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("r.txt","r",stdin) ;
#endif
mat a;
a.set_rank(100);
int T,n;
int cs=1;
scanf("%d",&T);
while(T--){
scanf("%d",&n);
for(regi i=1;i<=n;i++){
scanf("%d%d",&co[i].l,&co[i].r);
// if(co[i].l==100){ //不知道为什么不需要
// i--;
// n--;
// }
}
//构建初始矩阵
a.clear();
int cnt;
for(regi i=1;i<=100;i++){
a[i][i]=1;
cnt=6;
for(regi j=i+1;j<=100 && j<=i+6;j++){
a[i][j]=-1.0/6;
cnt--;
}
a[i][i]-=1.0*cnt/6;
a[i][101]=1;
}
a[100][100]=1;
a[100][101]=0;//dp[100+1]=0; 清掉常数,dp[l]=dp[r]
//梯子 蛇
for(regi i=1;i<=n;i++){
a.row_times(co[i].l,0);//行清零
a[co[i].l][co[i].r]=-1;
a[co[i].l][co[i].l]=1;
}
a.find_res();
a.print_res(dp);
// a.print_debug();
printf("Case %d: %.8f\n",cs++,dp[1]);
}}