题目链接
题意:给定一个(n imes m)的矩阵,每次可以向→↓←移动一格,也可以原地不动,求从((x,y))到最后一行的期望步数。
此题标签(DP)
看到上面这个肯定会想到
方法一: (f[i][j])表示表示从((x,y))走到((i,j))的期望步数,正推
方法二: (f[i][j])表示从((i,j))走到最后一行的期望步数,倒推
事实上,方法二更优秀。
因为如果用方法一,我们要求的答案就是(frac{sum f[ ext{最后一行}][ ext{每个位置}]}{m})
而如果我们用方法二,答案就是(f[x][y]),省时又省力。
所以我们就用方法二。
然而上面两种方案都是有后效性的,因为(f[i][j])取决与(f[i-1][j]( ext{或}f[i+1][j],f[i][j-1],f[i][j+1])甚至他本身,我们无法确定一个正确的递推顺序。
这时就要用(DP)套高斯消元了。
显然(f[ ext{最后一行}][ ext{每个位置}])都为(0),于是枚举行,从(n-1)枚举到(x)。
先把状态转移方程写出来。
(f[i][j]=egin{cases} frac{1}{3}(f[i][j]+f[i][j+1]+f[i+1][j]) (j=1)\ frac{1}{4}(f[i][j-1]+f[i][j]+f[i][j+1]+f[i+1][j]) (j!=1,j!=m)\ frac{1}{3}(f[i][j]+f[i][j-1]+f[i+1][j]) (j=m)end{cases})
可以发现,方程中(f[i+1][j])固定出现,而这是我们已知的(最后一行均为(0),一行一行往上倒推),于是考虑移项。
以(j=1)为例,
移项得
于是我们便得到了(m)个一次方程,用高斯消元即可解出每一行所有的(f)值。
高斯消元理论复杂度(O(n^3)),但是我们仔细观察本题,每行事实上只有两个数需要消元,所以我们可以在(O(m))的时间复杂度里完成高斯消元,算法总时间复杂度(O(nm^2))。
#include <cstdio>
#include <cstdlib>
const int MAXN = 1010;
double f[MAXN][MAXN];
double M[MAXN][MAXN];
int n, m, x, y;
void Gauss(){ //高斯消元
for(int i = 1; i <= m; ++i){
double tmp = 1.0 / M[i][i]; //系数化一
M[i][i] *= tmp; M[i][m + 1] *= tmp;
if(i == m) break;
M[i][i + 1] *= tmp;
tmp = M[i + 1][i] / M[i][i]; //下一行消掉该元
M[i + 1][i] -= tmp * M[i][i]; M[i + 1][i + 1] -= tmp * M[i][i + 1]; M[i + 1][m + 1] -= tmp * M[i][m + 1];
}
for(int i = m - 1; i; --i) M[i][m + 1] -= M[i + 1][m + 1] * M[i][i + 1]; //回代
}
int main(){
scanf("%d%d%d%d", &n, &m, &x, &y);
for(int i = n - 1; i >= x; --i){
M[1][1] = -1.0 + 1.0 / 3; //
M[1][2] = 1.0 / 3;
for(int j = 2; j < m; ++j){
M[j][m + 1] = (-f[i + 1][j]) / 4.0 - 1;
M[j][j] = -1.0 + 1.0 / 4;
M[j][j - 1] = M[j][j + 1] = 1.0 / 4;
}
M[m][m] = -1.0 + 1.0 / 3;
M[m][m - 1] = 1.0 / 3;
if(m == 1) M[1][1] = -1.0 + 1.0 / 2;
M[1][m + 1] = (-f[i + 1][1]) / 3.0 - 1;
M[m][m + 1] = (-f[i + 1][m]) / 3.0 - 1; //构建矩阵
if(m == 1) M[m][m + 1] = (-f[i + 1][m]) / 2.0 - 1; //特判$m=1$的情况
Gauss();
for(int j = 1; j <= m; ++j)
f[i][j] = M[j][m + 1];
}
printf("%.10lf", f[x][y]);
return 0;
}