[lnsyoj4079/luoguP6899]Pachinko

题意

一个包括空地、障碍和空洞的 H×W 地图,从第一列随机选取空地作为起始位置,到达某个空洞后停止运动。给定向上下左右移动的概率比 pu:pd:pl:pr,求在每个空洞停止运动的概率为多少。

sol

由于每次到达空洞后即会停止运动,因此到达空洞的期望次数即为到达每个空洞的概率。记 fi,j 表示到达 (i,j) 的次数的期望,就可以很容易的推出转移方程:

fi,j=fi+1,jpupsum+fi1,jpdpsum+fi,j+1plpsum+fi,j1prpsum+[i=1]1stcnt

stcnt 为第一行空地的数量,psum 为可以转移到的方向的概率比之和)

需要注意一点:当出现某一列全为 0 无法消元时,对答案并无影响,因此 continue 即可。

这样,我们只需要对列出的方程进行高斯消元即可解出答案……吗?

我们发现,这样一来,我们所求的未知数数量为 HW 个,而高斯消元的复杂度是 O(n3) 的,因此时间复杂度就达到了 O((HW)3),空间复杂度达到了 O((HW)2),无法接受。

不过,我们的增广矩阵中,最多只会在对角线左右 ±W 的位置出现非零数字,这是一个非常经典的优化 band-matrix。在消元和回代时,只向后修改 W 列即可,即使是矩阵中出现 0,需要换行时,也最多只需修改 (2W)×(2W) 的矩阵即可,而不需要修改整个矩阵;而且,我们可以为每一个可能出现非零数字的位置记录一个偏移量,这样时间复杂度就优化到了 O(HW3),空间复杂度优化到了 O(HW2)

注意:如果 WA on #9, Line 13,说明是被卡精度了;如果结果输出了 -nan,请改变写法,不要直接使用 im+j 作为编号存储,而是对每一个非障碍方格进行编号。

代码

#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>

using namespace std;

const int N = 25, M = 10005;
const long double eps = 1e-10;

int n, m, p[4];
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int pfrom[4] = {1, 0, 3, 2};
long double coeff[N * M][N * 4];
long double ans[N * M];
int psum[N * M];
int id[M][N];
char g[M][N];
int idx;

long double &c(int a, int b){
    return coeff[a][b - max(0, a - 2 * m)];
}

void gauss(){
    for (int i = 1; i <= idx; i ++ ){
        if (abs(c(i, i)) <= eps) continue;
        int ed = min(idx, i + 2 * m);
        ans[i] /= c(i, i);
        for (int j = ed; j >= i; j -- )
            c(i, j) /= c(i, i);
        for (int j = i + 1; j <= min(i + m, idx); j ++ ){ 
            
            ans[j] -= c(j, i) * ans[i];
            for (int k = ed; k >= i; k -- ) c(j, k) -= c(j, i) * c(i, k);
        }


    }

    for (int i = idx; i; i -- ){
        for (int j = i + 1; j <= min(idx, i + 2 * m); j ++ )
            ans[i] -= c(i, j) * ans[j];
    }
}

int main(){
    scanf("%d%d", &m, &n);
    for (int i = 0; i < 4; i ++ ) scanf("%d", &p[i]);
    for (int i = 0; i < n; i ++ ) scanf("%s", g[i]);

    int stcnt = 0;
    for (int i = 0; i < m; i ++ ) stcnt += (g[0][i] == '.');

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ ){
            id[i][j] = (g[i][j] == 'X') ? -1 : ++ idx;
            if (id[i][j] == -1) continue;
            for (int u = 0; u < 4; u ++ ) {
                int sx = i + dx[u], sy = j + dy[u];
                if (sx < 0 || sx >= n || sy < 0 || sy >= m || g[sx][sy] == 'X') continue;
                psum[id[i][j]] += p[u];
            }
        }

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ ){
            if (id[i][j] == -1) continue;
            if (!i && g[i][j] == '.') ans[id[i][j]] = (long double) 1 / stcnt;
            for (int u = 0; u < 4; u ++ ){
                int sx = i + dx[u], sy = j + dy[u];
                int px = pfrom[u];
                if (sx < 0 || sx >= n || sy < 0 || sy >= m || g[sx][sy] == 'X' || g[sx][sy] == 'T') continue;
                int sid = id[sx][sy];
                c(id[i][j], sid) = (long double) -p[px] / psum[sid];
            }
            c(id[i][j], id[i][j]) = 1;
        }

    gauss();

    for (int i = 0; i < n; i ++ )
        for (int j = 0; j < m; j ++ )
            if (g[i][j] == 'T') printf("%.9Lf\n", ans[id[i][j]]);

    return 0;
}
posted @   是一只小蒟蒻呀  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示