頂点が $n$ 個の木があります。辺はコストのある有向辺です。
どれかの頂点をゴールとしたときに、どの頂点からスタートしてもコストが $d$ 以内になるとき、
辺の向きを反転しなければならない回数を最小化したいです。その最小値を求めてください。
・$2 \le n \le 10^{5}$
・$0 \le$ 各辺のコスト $\le 10^{3}$
・$1 \le d \le 10^{9}$
木は、来たところを覚えておいて、次に行く際に来たところには行かないようにすれば、どこをスタートにしていても全部辿れる。
---
木の両端というのを考える。
グラフが無向辺だったとして、移動コストが最大となる 2 頂点を木の両端とする。
処理の流れ:
1. 木の両端(2 個)を求める。
2. 各頂点から両端までの移動コスト(2 頂点分)を求める。
3. 各頂点をゴールとしたとき、全ての頂点から辿りつけるように辺を反転させなければならない回数を求める。
4. 移動コストが $d$ 以内に収まるゴールのうち、反転回数の最小値を求める。
処理の詳細:
1, 2.
適当な頂点(下のプログラムでは 0 番)から各頂点への移動コストを求める。
それが最大となる頂点が両端のひとつ(u1)になる。
u1 から各頂点への移動コストを求める。
それが最大となる頂点が、両端のもうひとつ(u2)になる。
各頂点から両端までの移動コスト(cost1, cost2)は、両端を求めようとすると副次的に計算できている。
3.
適当な頂点(下のプログラムでは 0 番)をゴールとしたとき、
全ての頂点からゴールに辿りつけるように辺を反転させなければならない回数を求める(flip[0番])。
残りの頂点をゴールとしたときの反転すべき回数は、この結果を使うことができる。
辺の向きどおりに移動するときは結果を -1 し、逆向きに移動するときは結果を +1 する。
4.
移動コストの両方が $d$ 以下なら、反転回数の最小値と比較する。
#include <iostream>
#include <algorithm>
using namespace std;
using i64 = long long;
struct edge { int to; i64 cost; bool reversed; };
constexpr int inf = 987654321;
vector<vector<edge>> G;
vector<int> flip;
vector<i64> cost1, cost2;
// 移動時間の表を埋める
void dfs1(vector<i64> &cost, int v, int prev) {
for(edge e : G[v]) {
if(e.to == prev) { continue; }
cost[e.to] = cost[v] + e.cost;
dfs1(cost, e.to, v);
}
}
// 反転回数を計算する
int dfs2(int v, int prev) {
int res = 0;
for(edge e : G[v]) {
if(e.to == prev) { continue; }
res += dfs2(e.to, v) + !e.reversed;
}
return res;
}
// 反転回数の表を埋める
void dfs3(vector<int> &flip, int v, int prev) {
for(edge e : G[v]) {
if(e.to == prev) { continue; }
int dif = e.reversed ? +1 : -1;
flip[e.to] = flip[v] + dif;
dfs3(flip, e.to, v);
}
}
int main(void) {
int n; i64 d; scanf("%d%lld", &n, &d);
G.assign(n, vector<edge>());
for(int i=0; i<n-1; ++i) {
int a, b; i64 c; scanf("%d%d%lld", &a, &b, &c);
--a; --b;
G[a].push_back(edge({b, c, false}));
G[b].push_back(edge({a, c, true}));
}
// 木の直径の両端(u1, u2)を求め、
// i番から u1, u2 への移動時間を求める(cost1, cost2)
// そのためにまず 0番から各頂点への移動時間を求める。
// それが最大となる頂点が両端のひとつ(u1)である。
cost1.assign(n, 0);
dfs1(cost1, 0, -1);
int u1 = max_element(begin(cost1), end(cost1)) - begin(cost1);
cost1[u1] = 0;
dfs1(cost1, u1, -1);
int u2 = max_element(begin(cost1), end(cost1)) - begin(cost1);
cost2.assign(n, 0);
dfs1(cost2, u2, -1);
flip.assign(n, 0);
// 0番に向かうために辺を反転させる回数を求める
flip[0] = dfs2(0, -1);
// 0番の結果を使って i番に向かうために辺を反転させる回数を求める
dfs3(flip, 0, -1);
int mini = inf;
for(int i=0; i<n; ++i) {
if(cost1[i] > d || cost2[i] > d) { continue; }
mini = min(mini, flip[i]);
}
if(mini == inf) { mini = -1; }
printf("%d\n", mini);
return 0;
}
(ΦωΦ)<
つづく