@topcoder - SRM614D1L3@ TorusSailing


@description@

给定一个 N*M 的方格图,某人从 (0, 0) 出发想要走到 (goalX, goalY)。

假如该人在 (x, y),他会等概率地走向 ((x + 1) mod N, y) 或 (x, (y + 1) mod M)。

求到达终点的期望步数。

原题链接。

@solution@

显然可以列出期望的 dp 方程 dp[x][y] = (dp[(x+1) mod N][y] + dp[x][(y+1) mod M])/2 + 1。
发现要用高斯消元,而普通的高斯消元 O(N^6) 的复杂度太高,无法通过。

注意到我们可以先人工合并一些方程。
具体操作是,保留一些量作为高斯消元的变量(此处我们选择与 (goalX, goalY) 同行与同列的量),将其视作常量。
然后利用转移图的特殊性质(此处转移图是个网格图),将其他量用这些量表示出来。
我们可以从 (goalX - 1, goalY - 1) 从下往上,自右往左依次得到其他量用这些量表示出来的结果。

这一部分的复杂度是 O(N^3),之后的高斯消元复杂度也为 O(N^3),我们就可以通过该题了。

@accepted code@

#include <cmath>
#include <cstdio>
#include <algorithm>
using namespace std;

class TorusSailing{
    private:
        #define MAXN (200)
        struct node{
            double a[MAXN], b; int cnt;
            node() {}
            node(int n) {
                cnt = n, b = 0;
                for(int i=0;i<n;i++)
                    a[i] = 0;
            }
            friend node operator + (const node &x, const node &y) {
                node z(x.cnt); z.b = x.b + y.b;
                for(int i=0;i<x.cnt;i++) z.a[i] = x.a[i] + y.a[i];
                return z;
            }
            friend node operator + (const node &x, const double &k) {
                node z = x; z.b += k; 
                return z;
            }
            friend node operator / (const node &x, const double &k) {
                node z(x.cnt); z.b = x.b / k;
                for(int i=0;i<x.cnt;i++) z.a[i] = x.a[i]/k;
                return z;
            }
        }a[MAXN][MAXN];
        
        double A[MAXN][MAXN];
        void gauss(int n, int m) {
            int r = 0, c = 0;
            while( r < n && c < m ) {
                int mxr = r;
                for(int i=r+1;i<n;i++)
                    if( fabs(A[i][c]) >= fabs(A[mxr][c]) )
                        mxr = i;
                if( r != mxr ) {
                    for(int j=c;j<m;j++)
                        swap(A[r][j], A[mxr][j]);
                }
                if( A[r][c] ) {
                    double k = A[r][c];
                    for(int j=c;j<m;j++)
                        A[r][j] /= k;
                    for(int i=0;i<n;i++) {
                        if( i == r ) continue;
                        k = A[i][c];
                        for(int j=c;j<m;j++)
                            A[i][j] -= k*A[r][j];
                    }
                    r++;
                }
                c++;
            }
        }
    public:
        double expectedTime(int N, int M, int goalX, int goalY) {
            int K = (N - 1) + (M - 1);
            for(int i=0;i<N-1;i++)
                a[i][M-1] = node(K), a[i][M-1].a[i] = 1;
            for(int j=0;j<M-1;j++)
                a[N-1][j] = node(K), a[N-1][j].a[j+N-1] = 1;
            a[N-1][M-1] = node(K);
            for(int j=M-2;j>=0;j--)
                for(int i=N-2;i>=0;i--)
                    a[i][j] = (a[i+1][j] + a[i][j+1]) / 2 + 1;
            for(int i=0;i<N-1;i++) {
                node b = (a[i][0] + a[i+1][M-1]) / 2 + 1;
                for(int j=0;j<K;j++) A[i][j] = -b.a[j];
                A[i][i]++, A[i][K] = b.b;
            }
            for(int j=0;j<M-1;j++) {
                node b = (a[0][j] + a[N-1][j+1]) / 2 + 1;
                for(int i=0;i<K;i++) A[j+N-1][i] = -b.a[i];
                A[j+N-1][j+N-1]++, A[j+N-1][K] = b.b;
            }
            gauss(K, K + 1);
            int sx = N - goalX - 1, sy = M - goalY - 1;
            double ans = a[sx][sy].b;
            for(int i=0;i<a[sx][sy].cnt;i++)
                ans += a[sx][sy].a[i] * A[i][K];
            return ans;
        }
};

@details@

事实上,这道题感觉和 PKUWC2018 那道高消的优化思路有点类似(用合并方程的思想逐渐把未知量消掉)。。。
不过也可能是我联想能力太强。。。

posted @ 2020-01-08 15:36  Tiw_Air_OAO  阅读(...)  评论(...编辑  收藏