题解:AcWing 290. 坏掉的机器人

问题分析

这种在矩阵上移动求 Min、Max、Sum 的问题大多可使用动态规划(DP)解决,例如 CSP2020 - J 的第 4 题“方格取数”。

状态转移方程

这里采用倒推的方式,设 \(dp[i][j]\) 为从 \((i, j)\) 出发,走到第 \(N\) 行的期望步数,而非从 \((x, y)\)\((i, j)\) 的期望步数,避免问题复杂化。状态转移方程如下:

\[\begin{cases} dp[i][j] = \frac{1}{3}(dp[i][j] + dp[i][j + 1] + dp[i + 1][j]) + 1, & \text{if } j = 1 \\ dp[i][j] = \frac{1}{3}(dp[i][j] + dp[i][j - 1] + dp[i + 1][j]) + 1, & \text{if } j = M \\ dp[i][j] = \frac{1}{4}(dp[i][j] + dp[i][j - 1] + dp[i][j + 1] + dp[i + 1][j]) + 1, & \text{otherwise} \end{cases} \]

其中,当 \(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\) 开始倒推,可解决行与行之间的递推。对于列与列之间的依赖关系,可使用高斯消元法解决。

化简状态转移方程
将原状态转移方程化简可得:

\[\begin{cases} \frac{2}{3}dp[i][j] = \frac{1}{3}dp[i][j + 1] + \frac{1}{3}dp[i + 1][j] + 1, & \text{if } j = 1 \\ \frac{2}{3}dp[i][j] = \frac{1}{3}dp[i][j - 1] + \frac{1}{3}dp[i + 1][j] + 1, & \text{if } j = M \\ \frac{3}{4}dp[i][j] = \frac{1}{4}dp[i][j - 1] + \frac{1}{4}dp[i][j + 1] + \frac{1}{4}dp[i + 1][j] + 1, & \text{otherwise} \end{cases} \]

将已知数和未知数分开:

\[\begin{cases} \frac{2}{3}dp[i][j] - \frac{1}{3}dp[i][j + 1] = \frac{1}{3}dp[i + 1][j] + 1, & \text{if } j = 1 \\ \frac{2}{3}dp[i][j] - \frac{1}{3}dp[i][j - 1] = \frac{1}{3}dp[i + 1][j] + 1, & \text{if } j = M \\ \frac{3}{4}dp[i][j] - \frac{1}{4}dp[i][j - 1] - \frac{1}{4}dp[i][j + 1] = \frac{1}{4}dp[i + 1][j] + 1, & \text{otherwise} \end{cases} \]

系数矩阵

假设矩阵有 \(m = 5\) 列,其 \(5\times5\) 的系数矩阵如下:

\[\begin{bmatrix} \frac{2}{3} & -\frac{1}{3} & 0 & 0 & 0 & | & \frac{1}{3}dp[i + 1][1] + 1 \\ -\frac{1}{4} & \frac{3}{4} & -\frac{1}{4} & 0 & 0 & | & \frac{1}{4}dp[i + 1][2] + 1 \\ 0 & -\frac{1}{4} & \frac{3}{4} & -\frac{1}{4} & 0 & | & \frac{1}{4}dp[i + 1][3] + 1 \\ 0 & 0 & -\frac{1}{4} & \frac{3}{4} & -\frac{1}{4} & | & \frac{1}{4}dp[i + 1][4] + 1 \\ 0 & 0 & 0 & -\frac{1}{3} & \frac{2}{3} & | & \frac{1}{3}dp[i + 1][5] + 1 \end{bmatrix} \]

化简后为:

\[\begin{bmatrix} 2 & -1 & 0 & 0 & 0 & | & dp[i + 1][1] + 3 \\ -1 & 3 & -1 & 0 & 0 & | & dp[i + 1][2] + 4 \\ 0 & -1 & 3 & -1 & 0 & | & dp[i + 1][3] + 4 \\ 0 & 0 & -1 & 3 & -1 & | & dp[i + 1][4] + 4 \\ 0 & 0 & 0 & -1 & 2 & | & dp[i + 1][5] + 3 \end{bmatrix} \]

高斯消元法

高斯消元法用于解决 \(N\) 元一次方程组,需要一个系数矩阵,具体算法过程分三步:

  1. 找到第 \(i\) 列最大的数,并将其所在行挪到当前最靠上的一行,即第 \(i\) 行。
  2. 将该数变为 \(1\)(将第 \(i\) 行所有数除以该数)。
  3. 将所有靠下行第 \(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;
}
posted @ 2025-05-17 14:37  _Charllote  阅读(27)  评论(0)    收藏  举报