POJ 3669 Meteor Showerをbfsを使わずに解いてみるよ!

POJ3669といえば蟻本初級編章末でbfs問題として紹介されている。
事実bfsで解けるし、そのコードはこれが綺麗。今回の解法でも非常に参考になった。

さて、この問題、以前東方AIに使ったDP的な方法で解けるように見えた。
つまり移動可能な領域を探索深さが進むにつれ拡張(場合によっては縮小)していくことで、深さtにおいてある地点に移動可能かどうか知るという方法である。

#include <iostream>
#include <stdio.h>
using namespace std;

const int INF = 100000000;
const int N = 310;
const int MAX_T = 1000;
int grid[N][N];
bool dist[N][N][2];
int dx[] = {0, 0, 0, 1, -1};
int dy[] = {0, 1, -1, 0, 0};

int solve(){
    if(grid[1][1] == 0)
        return -1;
    dist[1][1][0] = true;
    for(int t=1; t<MAX_T; t++)for(int i=1; i<N-1; i++)for(int j=1; j<N-1; j++){
        if(dist[i][j][(t+1)%2]){
            for(int d=0; d<5; d++){
                int nx = i+dx[d], ny = j+dy[d];
                if(t < grid[nx][ny]){
                    dist[nx][ny][t%2] = true;
                    if(grid[nx][ny] == INF)
                        return t;
                }else{
                    dist[nx][ny][t%2] = false;
                }
            }
        }
    }
    return -1;
}

int main(int argc, const char * argv[]){
    for(int i=0; i<N; i++)
        for(int j=0; j<N; j++)
            grid[i][j] = INF;
    for(int i=0; i<N; i++){
        grid[0][i] = 0;
        grid[i][0] = 0;
    }
    int M;
    scanf("%d", &M);
    int X, Y, T;
    for(int m=0; m<M; m++){
        scanf("%d%d%d", &X, &Y, &T);
        X++; Y++;
        for(int d=0; d<5; d++){
            int& g = grid[X+dx[d]][Y+dy[d]];
            g = min(g, T);
        }
    }
    printf("%d\n", solve());
    return 0;
}

このアルゴリズムの計算量はO(N * N * T), N=高さ, 幅, T = 最大深さである。一般にbfsの計算量は状態数×遷移数であるが、これも同様にO(N * N * T * 5)でよく一致している。

この方法は実は典型的なdfs問題である迷路の最短経路問題でも用いることが出来る。
しかし、この場合も安直に移動可能領域を更新するなら同様に計算量はO(N * N * T)である。一方bfsではO(N * N * 4)で圧倒的に速い。

これは簡単な枝刈りで解消される。迷路問題の場合、更新される移動可能領域は必ずその領域のエッジ部(移動可能領域と不可領域の境界)である。このエッジ部をqueueに入れて管理すればbfsと全く同様の動きをすることが分かる(深さがイメージしやすい分bfsの理解がしやすいかも?)。

復習をすると新たな気付きがあって良い。