Yukicoder 魔法使いのすごろく🎲

,

http://yukicoder.me/problems/no/463

DPに見せかけてそうでないやつ好き。

solution

魔法を使った後の状態は連立方程式を立てて行列。使う前の状態はDP。$O(M^3 + N)$。

折り返しがない部分について。 さいころを振れば$\mathrm{dp}(i) = \frac{1}{m} \sum_{i+1 \le j \le i+m} (c_j + \mathrm{dp}(j))$の単純なDP。 魔法を使う場合は$\mathrm{dp}(i) = \min_{i+1 \le j \le i+m} (c_j + \mathrm{dp}(j))$。 これらは適切にすれば$O(N)$になる。

折り返しがある部分について。 まだ魔法を使ってない場合は使ってゴールに飛べばよい。 使ってしまっている場合が問題。 $\mathrm{dp}(i)$が依存するのが$j \lt i$な$\mathrm{dp}(j)$になりうるので、整礎でなくDPでは計算できない。 しかし方程式は立っているので、これを掃き出し法などで解けば求まる。$O(M^3)$。

implementation

#include <cstdio>
#include <vector>
#include <cmath>
#include <cassert>
#define repeat(i,n) for (int i = 0; (i) < int(n); ++(i))
#define repeat_from(i,m,n) for (int i = (m); (i) < int(n); ++(i))
#define repeat_reverse(i,n) for (int i = (n)-1; (i) >= 0; --(i))
typedef long long ll;
using namespace std;
template <class T> void setmin(T & a, T const & b) { if (b < a) a = b; }
template <typename X, typename T> auto vectors(X x, T a) { return vector<T>(x, a); }
template <typename X, typename Y, typename Z, typename... Zs> auto vectors(X x, Y y, Z z, Zs... zs) { auto cont = vectors(y, z, zs...); return vector<decltype(cont)>(x, cont); }

using real = double;
const real eps = 1e-14;

vector<vector<real> > operator * (vector<vector<real> > const & p, vector<vector<real> > const & q) {
    int n = p.size();
    vector<vector<real> > r(n, vector<real>(n));
    repeat (y,n) repeat (z,n) repeat (x,n) r[y][x] += p[y][z] * q[z][x];
    return r;
}
vector<real> operator * (vector<vector<real> > const & p, vector<real> const & q) {
    int n = p.size();
    vector<real> r(n);
    repeat (y,n) repeat (z,n) r[y] += p[y][z] * q[z];
    return r;
}
vector<vector<real> > unit_matrix(int n) {
    vector<vector<real> > e(n, vector<real>(n));
    repeat (i,n) e[i][i] = 1;
    return e;
}
vector<vector<real> > zero_matrix(int n) {
    vector<vector<real> > o(n, vector<real>(n));
    return o;
}

vector<real> gaussian_elimination(vector<vector<real> > f, vector<real> x) {
    int n = x.size();
    repeat (y,n) {
        int pivot = y;
        while (pivot < n and abs(f[pivot][y]) < eps) ++ pivot;
        assert (pivot < n);
        swap(f[y], f[pivot]);
        x[y] /= f[y][y];
        repeat_from (x,y+1,n) f[y][x] /= f[y][y];
        f[y][y] = 1;
        repeat (ny,n) if (ny != y) {
            x[ny] -= f[ny][y] * x[y];
            repeat_from (x,y+1,n) f[ny][x] -= f[ny][y] * f[y][x];
            f[ny][y] = 0;
        }
    }
    return x;
}

int main() {
    int n, m; scanf("%d%d", &n, &m);
    vector<int> c(n); repeat (i,n-2) scanf("%d", &c[i+1]);
    vector<real> init; {
        vector<vector<real> > f = vectors(m-1, m-1, real());
        vector<real> v = vectors(m-1, real());
        repeat (y,m-1) {
            f[y][y] += m;
            repeat (x,m) {
                int z = min(y+x+1, 2*(m-1)-(y+x+1));
                if (z == m-1) continue;
                f[y][z] -= 1;
                v[y] += c[n-m+z];
            }
        }
        init = gaussian_elimination(f, v);
    }
    vector<real> dp1(n);
    repeat_reverse (i,n) {
        if (i == n-1) {
            // zero
        } else if (n-m <= i) {
            dp1[i] = init[i-(n-m)];
        } else {
            repeat (j,m) dp1[i] += (c[i+j+1] + dp1[i+j+1]) / m;
        }
    }
    vector<real> dp0(n);
    repeat_reverse (i,n) {
        if (n-m-1 <= i) {
            // zero
        } else {
            repeat (j,m) dp0[i] += (c[i+j+1] + dp0[i+j+1]) / m;
            repeat (j,m) setmin(dp0[i], c[i+j+1] + dp1[i+j+1]);
        }
    }
    printf("%.12lf\n", dp0[0]);
    return 0;
}