题意:
有n个节点,要从第一个节点移动到第n个节点,每次移动可能前进或者后退m步,概率通过给出的n×m的矩阵计算,问从第一个点道第n个点所走步数的数学期望。
思路:
n个未知数,设F[i]表示从第i点道第n点的期望的步数,则F[n] = 0, 可以列出n-1个F[i]和其他的F[]关系的方程。
n有50000,直接消元不仅会TLE,还会MLE,这里考虑到第i个方程至多只要前m个方程就能将该方程的前i-1消为0,所以可以滚动,每次只保存m+1个方程。然后模拟高斯消元即可。另外由于要求得最终是F[1],而在滚动过程中F[1]对应矩阵中的行一般会被覆盖掉,这里可以另外弄一行存F[1]对应的行,每次消元时记得把这行也更新一下即可。
代码:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <queue>
#include <string>
#include <map>
#include <cmath>
using namespace std;
#define N 101000
#define M 30
const double eps = 1e-8;
double c[N][M], mat[M][N];
double fir[N];
int n, m;
int dcmp(double x)
{
if(abs(x) < eps) return 0;
return x > 0 ? 1 : -1;
}
double getP(int i, int j)
{
if(abs(i-j) > m) return 0;
double sum = 0;
for(int k = 1; k <= m; k++)
{
sum += c[i][k];
}
if(i-j >= 1)
{
return 0.3*c[i][i-j]/(1+sum);
}
else return 0.7*c[i][j-i]/(1+sum);
}
void getRow(double* a, int row)
{
double sum = 0;
for(int i = max(1, row-m); i <= min(n, row+m); i++)
{
if(i != row)
{
a[i] = -getP(row, i);
sum += a[i];
}
}
a[n+1] = 1;
a[row] = -sum;
}
int main()
{
//freopen("/home/zfh/桌面/out", "r", stdin);
while(scanf("%d%d", &n, &m) && n)
{
memset(mat, 0, sizeof(mat));
memset(fir, 0, sizeof(fir));
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
scanf("%lf", &c[i][j]);
}
}
int row = 0, col = 1;
getRow(mat[0], 1);
getRow(fir, 1);
for(int i = 2; i < n; i++, col++)
{
int cur = (i-1)%(m+1);
getRow(mat[cur], i);
int p = row;
int st = max(i-m, 1);
for(int j = 1; j <= min(i-1, m); j++)
{
double temp = -mat[cur][st]/mat[p][st];
for(int k = st++; k <= min(st+2*m+1, n); k++)
{
mat[cur][k] += temp*mat[p][k];
}
mat[cur][n+1] += temp*mat[p][n+1];
p = (p+1)%(m+1);
}
double temp = -fir[i]/mat[cur][i];
for(int j = i; j <= min(i+m, n); j++)
{
fir[j] += temp*mat[cur][j];
}
fir[n+1] += temp*mat[cur][n+1];
if(i > m)
row = (row+1)%(m+1);
}
printf("%.2lf\n", fir[n+1]/fir[1]);
}
return 0;
}