予鈴

アウトプットとメモ書きの中間みたいな記事がたくさん出ます。

E - Skiing と ポテンシャル

問題

atcoder.jp

解法の前のお勉強

辺のコストの持ち方以外は難しくない。制約的に、Dijkstra法を用いることで解ける。さて、問題はコストが負になりうるという点である。
Dijkstraアルゴリズムは、負の辺があると動作しない。ならば、負の辺を消してしまえば良い。


具体的には、ポテンシャルという概念を導入する。
ここでは、ながたかなさんの記事とできるだけ同じ記法を使って説明してみる。下の参考文献にもリンクがあります。

(感覚的には、最短経路の性質が壊れないように、適当な値を加えて辺を変える。多分)


頂点  x,y を結ぶ辺を c(x,y) とする。各頂点ごとにポテンシャル p_x,p_yを考え、ポテンシャル導入した辺を c'を次のように定義する。ただし、変更後の辺はコストが正になってほしいので、ポテンシャルは次のようにしておく。


 p_y - p_x \leq c(x,y)
 c'(x,y) = c(x,y) + p_x - p_y \geq 0


この辺を用いたときの最短経路の求め方をエミュレートしてみる。 \delta ' (i)は、上で定義した辺( c')を用いたときの、始点sから iまでの最短距離を表す。


 \delta '(u) +  c'(u,v) \geq \delta '(v)
 \delta '(u) +  c(u,v) + p_u - p_v  \geq \delta '(v)
 \delta '(u) +  c(u,v) + p_u \geq \delta '(v) + p_v


ここで、 D(i) = \delta '(i) + p_i と考えると


 D(u) + c(u,v) \geq D(v)


となって、置き換えると最短距離のまんまになる。辺のコストを書き換えただけなのに、元の辺のコストが現れた…だと


これだけだと? となるので、口語的な説明を追記してみる。

まずはじめに、 c(x,y) c'(x,y)の差は、 x,yのポテンシャルの差になる。頂点のポテンシャルの差分(始点と終点)のみに依存するため、変更後の辺( c')上で求めた最短路も、変更前の辺( c)上で求めた最短路になる。


始点と終点以外のポテンシャルの値は辺を移動するときに消えそうだな、という感じもするが、せっかくなのできちんと説明しておく。

さて、最終的にほしいのは D(u)ではなく、ポテンシャルを導入する前の最短経路 \delta (u)である。今までは始点を省略していたが、ここからは D(s,u) \delta (s,u)のように記述することにする。


結論から言うと、 \delta (s,u) = D(s,u) + p_u - p_s が成り立つ(らしい)。

正直全然わからないので、もう少し詳しく見てみる。

 s → v → uという最短路だったとしよう。 D(i,i) = 0とする。


 D(s,u) = D(s,s) + c'(s,v) + c'(v,u)
\qquad\quad = D(s,s) + c(s,v) + p_s - p_v  + c(v,u) + p_v - p_u


 D(s,u) = D(s,s) + c(s,v) + c(v,u) +  p_s - p_u
一方で、
 \delta(s,u) = \delta(s,s) + c(s,v) + c(v,u)


これを比較するとさっきの式がでる。
なるほど、たしかに端点だけを考慮するだけで良い。すごい。

解法

はい。それではようやく問題を解き始め
最大化なので、最小化に言い換えてDijkstra法を適用することにする。これに伴って、 X > Yのときは -1 * (H_X - H_Y) <  0,  X < Yのときは  -1 * (2H_X - 2H_Y) > 0と言い換えることができる。

さて、ここからポテンシャルを導入することを考える。


 p_v = H[v] として、ポテンシャルを導入してみる。


 H[X] > H[Y]のとき

 p_X - p_Y \leq  c(Y,X) = -1 * (2H_Y - 2H_X) = 2 * p_X - 2 * p_Y
 c'(y,x) =  2 * p_X - 2 * p_Y + p_Y - p_X  = p_X - p_Y > 0


 H[X] < H[Y]のとき

 p_X - p_Y \leq  c(Y,X) = -1 * (H_Y - H_X) = p_X - p_Y

 c'(y,x) =  p_X - p_Y + p_Y - p_X =  0

良さそう。この辺を使って解くと問題が解が得られる。

解法

#include <iostream>
#include <vector>
#include <queue>

using namespace std;

using Int = long long;
constexpr Int inf = (1LL  << 60);

inline Int potential(Int a, Int b,vector<Int>&H){
    
    if(H[a] > H[b])
    {
        return H[a] - H[b];
        
    } else
    {
        return 0LL;
    }
}

int main(){
    
    int N,M;
    cin >> N >> M;
    
    vector<Int>H(N);
    vector<vector<pair<int,Int>>>edge(N);
    
    for(auto& h : H){
        cin >> h;
    }
    
    for(int i = 0; i < M; ++i){
        int u,v; cin >> u >> v;
        --u; --v;
        edge[u].emplace_back(v,potential(v,u,H));
        edge[v].emplace_back(u,potential(u,v,H));
    }
    
    vector<Int>d(N,inf);
    d[0] = 0;
    
    priority_queue<pair<Int,int>,vector<pair<Int,int>>,greater<>> que;
    que.emplace(d[0],0);
    
    while(not que.empty()){
        auto [td,cv] = que.top();
        que.pop();
        
        if(d[cv]  <  td)
            continue;
        for(auto p : edge[cv]){
            int nv = p.first;
            Int cst = p.second;
            if(d[nv] <= cst + d[cv])
                continue;
            d[nv] = cst + d[cv];
            que.emplace( d[nv], nv);
        }
    }
    Int ans = 0;
    for(int i = 0; i < N; ++i)
        ans = max(ans, -1 * (d[i] - H[0] + H[i]));
    
    cout << ans << endl;
}


参考

すこし難しいがとても詳しい。
https://qiita.com/ngtkana/items/d7fc4463e56b966d1ebf

理解にとても役立った
niuez.hatenablog.com

やはり先人は偉大で、こういった記事で理解が何倍も早く進む。感謝します。

謝辞

この記事を書く(勉強する)に至って、Twitter上で質問に答えてくださった皆様に感謝します。
ありがとうございました。