题解:AcWing 290. 坏掉的机器人
问题分析
这种在矩阵上移动求 Min、Max、Sum 的问题大多可使用动态规划(DP)解决,例如 CSP2020 - J 的第 4 题“方格取数”。
状态转移方程
这里采用倒推的方式,设 \(dp[i][j]\) 为从 \((i, j)\) 出发,走到第 \(N\) 行的期望步数,而非从 \((x, y)\) 到 \((i, j)\) 的期望步数,避免问题复杂化。状态转移方程如下:
其中,当 \(j = 1\) 时不能向左移动;当 \(j = M\) 时不能向右移动。
特殊情况
当 \(M = 1\) 时,只能不动或向下移动,此时 \(dp[i][1] = \frac{1}{2}(dp[i][1] + dp[i + 1][1]) + 1\)。手动解方程可得 \(dp[i][1] = dp[i + 1][1] + 2\),那么答案可直接算出,第 \(X\) 行的结果为 \(2\times(N - X)\)。
后效性问题及解决方法
状态转移方程存在后效性,\(dp[i][j]\) 会用到 \(dp[i][j + 1]\),而 \(dp[i][j + 1]\) 也会用到 \(dp[i][j]\),构成了环。但该题没有向上移动,行与行之间没有依赖关系,第 \(i\) 行只会用到第 \(i + 1\) 行,不会用到第 \(i - 1\) 行,所以从 \(N\) 开始倒推,可解决行与行之间的递推。对于列与列之间的依赖关系,可使用高斯消元法解决。
化简状态转移方程
将原状态转移方程化简可得:
将已知数和未知数分开:
系数矩阵
假设矩阵有 \(m = 5\) 列,其 \(5\times5\) 的系数矩阵如下:
化简后为:
高斯消元法
高斯消元法用于解决 \(N\) 元一次方程组,需要一个系数矩阵,具体算法过程分三步:
- 找到第 \(i\) 列最大的数,并将其所在行挪到当前最靠上的一行,即第 \(i\) 行。
- 将该数变为 \(1\)(将第 \(i\) 行所有数除以该数)。
- 将所有靠下行第 \(i\) 列上的数变为 \(0\)(将所有行数比 \(i\) 大的行上的数都减去该行第 \(i\) 列数乘以第 \(i\) 行上与其在同列的数)。
最后可将系数矩阵转化为一个上三角矩阵,从而轻松求出未知数,且该题不存在无解或有多组解的情况。
由于 DP 状态转移方程在每一行上和高斯消元可求解的问题一样,是一个 \(N\) 元一次方程组,所以可以倒序遍历每一行,每一行再填写高斯消元系数矩阵进行求解。
#include <bits/stdc++.h>
using namespace std;
#define IF_ON_ONLINEOJ true
#define IF_ON_LUOGU true
#define JIAO_HU_TI false
namespace fast{
#if IF_ON_LUOGU == false
#pragma GCC optimize("Ofast,unroll-loops,no-stack-protector")
#endif
#if JIAO_HU_TI == false
#define endl '\n'
#endif
#define re register
#define il inline
#define ri register int
#if IF_ON_ONLINEOJ == true
constexpr auto asdfasdf=1<<20;char in[asdfasdf],out[asdfasdf],*p1=in,*p2=in,*p3=out;
#define getchar() (p1==p2&&(p2=(p1=in)+fread(in,1,asdfasdf,stdin),p1==p2)?EOF:*p1++)
#define flush() (fwrite(out,1,p3-out,stdout))
#define putchar(x) (p3==out+asdfasdf&&(flush(),p3=out),*p3++=(x))
class Flush{public:~Flush(){flush();}}_;
#endif
template<typename type>inline type read(type &x){x=0;bool flag(0);char ch=getchar();while(!isdigit(ch)) flag^=ch=='-',ch=getchar();while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();return flag?x=-x:x;}
template<typename type>inline void write(type x){x<0?x=-x,putchar('-'):0;static short Stack[50],top(0);do Stack[++top]=x%10,x/=10;while(x);while(top) putchar(Stack[top--]|48);}
inline char read(char &x){do x=getchar();while(isspace(x));return x;}
inline char write(const char &x){return putchar(x);}
inline void read(char *x){static char ch;read(ch);do *(x++)=ch;while(!isspace(ch=getchar())&&~ch);}
template<typename type>inline void write(type *x){while(*x)putchar(*(x++));}
inline void read(string &x){static char ch;read(ch),x.clear();do x+=ch;while(!isspace(ch=getchar())&&~ch);}
inline void write(const string &x){for(int i=0,len=x.length();i<len;++i)putchar(x[i]);}
template<typename type,typename...T>inline void read(type &x,T&...y){read(x),read(y...);}
template<typename type,typename...T>inline void write(const type &x,const T&...y){write(x),write(y...);}
}
using namespace fast;
const int N = 1e3 + 5;
using DB = double;
int n, m, x, y;
DB f[N][N], X[N][N];
void gaosi(){
for (int i = 1; i <= m; i ++ ){
DB t = X[i][i];
X[i][i] = 1.0; X[i][i + 1] /= t;
if (i < m) X[i][m + 1] /= t;
t = X[i + 1][i];
X[i + 1][i] -= t;
X[i + 1][i + 1] -= t * X[i][i + 1];
X[i + 1][m + 1] -= t * X[i][m + 1];
}
for ( int i = m ; i > 0 ; i -- ) {
X[i - 1][m + 1] -= X[i - 1][i] * X[i][m + 1];
X[i - 1][i] -= X[i - 1][i] * X[i][i];
}
}
signed main() {
read(n, m, x, y);
if (m == 1){
write((n - x) << 1, '\n');
return 0;
}
for (int i = n - 1; i >= x; i -- ){
X[1][1] = X[m][m] = 2.0 / 3.0;
X[1][2] = X[m][m - 1] = - 1.0 / 3.0;
X[1][m + 1] = f[i + 1][1] / 3.0 + 1.0;
X[m][m + 1] = f[i + 1][m] / 3.0 + 1.0;
for (int j = 2; j < m; j ++ ){
X[j][j - 1] = X[j][j + 1] = - 1.0 / 4.0;
X[j][j] = 3.0 / 4.0;
X[j][m + 1] = f[i + 1][j] / 4.0 + 1.0;
}
gaosi();
for (int j = 1; j <= m; j ++ )
f[i][j] = X[j][m + 1];
}
printf("%.10lf\n", f[x][y]);
return 0;
}

浙公网安备 33010602011771号