ferinの競プロ帳

競プロについてのメモ

square869120Contest #5 E - Broken Skateboard

問題ページ
最短路問題+kの条件なので dist[マスi][k] = 最短経路 というような拡張dijkstraをすればよさそう.

  • O(HW) 個の各頂点を始点として拡張dijkstra
    コンクリートのマスの数を N とすると O(H^2W^2N\log HWN)
  • 止まる頂点はコンクリートとゴールのマスのみ
    拡張dijkstraの頂点数,辺数を O(N^2) にできるので O(HWN^2 \log N)
  • 始点はコンクリートのみ
    コンクリート以外のマスの答えは (最寄りのコンクリートまでの距離) + (そのコンクリートからゴールの距離) なので全マスからdijkstraしなくともよい.O(N^3 \log N)
  • 終点からdijkstra
    多くの始点から1つの終点までの距離を求めるときに各始点からdijkstraをするのではなく,終点から1回dijkstraをすることで各頂点までの距離を求めることができる.O(N^2 \log N)
  • DP
    dijkstraの遷移を考えると dist[i][j] → dist[to][j-1] となる.毎回jが1減り,jが増えるような遷移が存在しないためこのグラフはDAGである.したがってDPで最短経路を求めることができる.O(N^2)

DAGでlog落とせるのに気づかなかった…

#include <bits/stdc++.h>  
using namespace std;  
using ll = long long;  
using PII = pair<ll, ll>;  
#define FOR(i, a, n) for (ll i = (ll)a; i < (ll)n; ++i)  
#define REP(i, n) FOR(i, 0, n)  
#define ALL(x) x.begin(), x.end()  
template<typename T> void chmin(T &a, const T &b) { a = min(a, b); }  
template<typename T> void chmax(T &a, const T &b) { a = max(a, b); }  
struct FastIO {FastIO() { cin.tie(0); ios::sync_with_stdio(0); }}fastiofastio;  
#ifdef DEBUG_   
#include "../program_contest_library/memo/dump.hpp"  
#else  
#define dump(...)  
#endif  
const ll INF = 1LL<<30;  
  
int main(void) {  
    ll h, w;  
    cin >> h >> w;  
    vector<string> s(h);  
    REP(i, h) cin >> s[i];  
  
    // コンクリートとゴールからなるグラフを作成  
    ll n = 0, gx = -1, gy = -1;  
    vector<vector<ll>> trans(h, vector<ll>(w, -1));  
    vector<PII> inv;  
    REP(i, h) REP(j, w) {  
        if(s[i][j] == '#') {  
            trans[i][j] = n++;  
            inv.push_back({i, j});  
        }  
        if(s[i][j] == 'G') gx = j, gy = i;  
    }  
    trans[gy][gx] = n++;  
    inv.push_back({gy, gx});  
  
    vector<vector<PII>> g(n);  
    REP(i, h) {  
        ll pre = -1;  
        REP(j, w) {  
            if(s[i][j] == '#' || s[i][j] == 'G') {  
                if(pre == -1) {  
                    pre = j;   
                    continue;  
                }  
                ll u = trans[i][pre], v = trans[i][j];  
                g[u].push_back({v, j-pre});  
                g[v].push_back({u, j-pre});  
                pre = j;  
            }  
        }  
    }  
    REP(i, w) {  
        ll pre = -1;  
        REP(j, h) {  
            if(s[j][i] == '#' || s[j][i] == 'G') {  
                if(pre == -1) {  
                    pre = j;  
                    continue;  
                }  
                ll u = trans[pre][i], v = trans[j][i];  
                g[u].push_back({v, j-pre});  
                g[v].push_back({u, j-pre});  
                pre = j;  
            }  
        }  
    }  
  
    // 終点からDPで最短距離を求める  
    vector<vector<ll>> dist(n+1, vector<ll>(n, INF));  
    REP(i, n+1) dist[i][trans[gy][gx]] = 0;  
    for(ll i=n; i>0; --i) {  
        REP(j, n) for(auto to: g[j]) {  
            chmin(dist[i-1][to.first], dist[i][j] + i*to.second);  
        }  
    }  
  
    // 氷のマスからスタートする場合  
    vector<vector<ll>> ans(h, vector<ll>(w, INF));  
    REP(i, n) {  
        ll x = inv[i].second, y = inv[i].first;  
        chmin(ans[y][x], dist[0][i]);  
        FOR(j, x+1, w) {  
            chmin(ans[y][j], dist[1][i] + j-x);  
            if(s[y][j] == '#' || s[y][j] == 'G') break;  
        }  
        FOR(j, y+1, h) {  
            chmin(ans[j][x], dist[1][i] + j-y);  
            if(s[j][x] == '#' || s[j][x] == 'G') break;  
        }  
        for(ll j=x-1; j>=0; --j) {  
            chmin(ans[y][j], dist[1][i] + x-j);  
            if(s[y][j] == '#' || s[y][j] == 'G') break;  
        }  
        for(ll j=y-1; j>=0; --j) {  
            chmin(ans[j][x], dist[1][i] + y-j);  
            if(s[j][x] == '#' || s[j][x] == 'G') break;  
        }  
    }  
  
    REP(i, h) REP(j, w) cout << (ans[i][j]==INF?-1:ans[i][j]) << (j==w-1?'\n':' ');  
    cout << flush;  
  
    return 0;  
}