Official
F - Exactly K Steps 2 Editorial
by
F - Exactly K Steps 2 Editorial
by
MMNMM
頂点 \(i\) からちょうど \(k\) 回移動して頂点 \(j\) に行くためのコストの総和を \(c ^ k _ {i,j}\) と書くことにします。 \((i,j)\) で添字づけられた列 \(c ^ k=(c ^ k _ {i,j}) _ {1\le i\le N,1\le j\le N}\) を考えます。 \(c ^ 1=(C _ {i,j}) _ {1\le i\le N,1\le j\le N}\) です。
\(c ^ a\) と \(c ^ b\) が求められているとき、次のようなアルゴリズムによって \(c ^ {a+b}\) を求めることができます。
- はじめ、\(x _ {i,j}\leftarrow\infty\) で初期化する。
- すべての \((i,j,k)\in\lbrace1,2,\ldots,N\rbrace ^ 3\) に対して以下を繰り返す。
- \(x _ {i,k}\leftarrow\min\lbrace x _ {i,k},c ^ a _ {i,j}+c ^ b _ {j,k}\rbrace\) で更新する。
- \(x\) が求める \(c ^ {a+b}\) である。
これは min-plus 半環での行列積と考えることもできます。
このアルゴリズムを使うことで、繰り返し二乗法によって答えを \(O(N ^ 3\log K)\) 時間で求めることができます。
実装例は以下のようになります。
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
int main() {
using namespace std;
unsigned N, K;
cin >> N >> K;
vector C(N, vector<unsigned long>(N));
for (auto&& row : C)
for (auto&& c : row)
cin >> c;
// a 回移動した結果と b 回移動した結果から a + b 回移動した結果を求める
const auto prod{[N, inf{ranges::max(C | views::join) * K}](const auto& lhs, const auto& rhs) {
// 十分大きな値で初期化しておく
vector ret(N, vector(N, inf));
for (const auto i : views::iota(0U, N))
for (const auto j : views::iota(0U, N))
for (const auto k : views::iota(0U, N))
ret[i][k] = min(ret[i][k], lhs[i][j] + rhs[j][k]);
return ret;
}};
auto ans(C);
--K;
// 繰り返し二乗法
while (K) {
if (K & 1)
ans = prod(ans, C);
C = prod(C, C);
K /= 2;
}
for (const auto i : views::iota(0U, N))
cout << ans[i][i] << endl;
return 0;
}
posted:
last update:
