AtCoder ABC291 F - Teleporter and Closed off

考えたこと

・幅優先探索やダイクストラ法では間に合わなそう

最短経路探索といえば幅優先探索やダイクストラ法だが、この問題では使っても間に合わなそうに見える。
これらのアルゴリズムは頂点や辺の変化に弱く、「特定の頂点が通れなくなったら」「特定の辺が通れなくなったら」などの変化を1つ与えただけで既存の経路探索結果は使えなくなってしまう。
探索し直す回数が100回程度なら間に合うが、今回は最大で約10^5回である。間に合うはずがない。

Mが最大10なので、一度探索した経路について通れなくなった都市の前後M点だけ再探索できないかとも考えてみたが、以下のようなケースに対応できない。
 ・N = 41
 ・M = 10
 ・経路1:1→11→21→31→41
 ・経路2:1→2→3→4→5→6→7→8→9→10→12→(以下略)
   ※11, 21, 31以外の全てを通るようにする
 ・この2つの経路しかない
この場合、都市21番を通れなくすると最短経路は経路2になるが、都市1番の段階で違う経路を通ることになるので、最短経路を部分的に再探索するのでは導出できない。

・特定の点を通るようにした最短経路は簡単に求めることができる

例えば1番から5番を通り10番へ向かう最短経路を求めたい場合には、
 ・1番から5番までの最短経路
 ・5番から10番までの最短経路
の和を取ることで求めることができる。
同様に、途中で2つの点(a, b)を通るようにしたければ
 ・1番から中継地点aまでの最短経路
 ・中継地点aから中継地点bまでの最短経路
 ・中継地点bから10番までの最短経路
の和を取ればいい。

・1番→中継a→(都市kを飛ばして直接)→中継b→N番とすればいい

都市kを通らないようにした最短経路を求めるならば、上記の「途中で2つの点(a, b)を通るときの最短経路」を求める方法が使える。
例えば
 ・N = 10
 ・M = 3
 ・k = 5
のとき、5番を通らないようにすればいいので、その選択肢は
  1番→(最短で)→3番→(1手で)→6番→(最短で)→10番
  1番→(最短で)→4番→(1手で)→6番→(最短で)→10番
  1番→(最短で)→4番→(1手で)→7番→(最短で)→10番
の3つしかない。この3経路の最短距離が答えとなる。
選択肢の総数はM * (M-1) / 2となるが、Mが最大10なので最大の選択肢の総数は45個である。2≦k≦N-1の全てのkに対してこの最大45個を総当たりするのは可能である。

よってこの問題は
 ① 1番から全ての点への最短経路を求める
 ② 全ての点からN番への最短経路を求める
 ③ 通らないkを1点決め、kを通らない経路(最大45通り)を総当たり
 ④ ③を2~N-1でループ
で解くことができる。①と②は幅優先探索でもダイクストラ法でもDPでも求めることができるのでお好みの方法で求めればいい。
②を求める場合は、始点をNにしてすべての辺を逆向きに貼りなおせば①と同様の方法で求めることができる。

書いたコードと提出結果

#include <bits/stdc++.h>

int main(){
    int N, M;
    std::cin >> N >> M;
    std::vector< std::string > S(N);
    for(int i=0; i<N; i++){
        std::cin >> S[i];
    }
    
    std::vector< int > dists(N, 1e9);
    dists[0] = 0;
    for(int i=1; i<N; i++){
        for(int j=0; j<M; j++){
            if(i-j-1 == -1){
                break;
            }
            if(S[i-j-1][j] == '1'){
                dists[i] = std::min(dists[i], dists[i-j-1] + 1);
            }
        }
    }
    
    std::vector< int > distt(N, 1e9);
    distt[N-1] = 0;
    for(int i=N-2; i>=0; i--){
        for(int j=0; j<M; j++){
            if(i+j+1 == N){
                break;
            }
            if(S[i][j] == '1'){
                distt[i] = std::min(distt[i], distt[i+j+1] + 1);
            }
        }
    }
    
    for(int i=1; i<N-1; i++){
        int min = 1e9;
        for(int a=i-M+1; a<=i-1; a++){
            if(a < 0 || dists[a] == 1e9){
                continue;
            }
            for(int b=i+1; a+M>=b; b++){
                if(b >= N || distt[b] == 1e9){
                    continue;
                }
                if(S[a][b-a-1] == '1'){
                    min = std::min(min, dists[a] + distt[b] + 1);
                }
            }
        }
        if(min == 1e9){
            std::cout << -1 << std::endl;
        }else{
            std::cout << min << std::endl;
        }
    }
    
    
    return 0;
}


この記事が気に入ったらサポートをしてみませんか?