---
lang: ja-jp
tags: APIO
---
# APIO 2020-B 都市の代表者の交換 (Swapping Cities)
不等号に注意
## 問題文
https://apio2020.toki.id/assets/statements/swap_ja.pdf
https://oj.uz/problem/view/APIO20_swap
$N$ 頂点 $M$ 辺の重み付き無向グラフが与えられる。 $Q$ 回の以下のクエリにオンラインで答えよ。
頂点 $X, Y$ から同時に車が $1$ 台出発し、頂点 $Y, X$ に向かう。
しかし、道は車 1 台分の幅しかないので、途中で方向を変えたり、すれ違ったりできない。
$2$ 台の車が通る辺の重みの最大値の最小値は?
$N ≤ 10^5$
$M ≤ 2 \times 10^5$
$Q ≤ 2 \times 10^5$
## 考察
最大値の最小値なので答え決め打ち二分探索 [典型]
重み $K$ 以下の辺のみからなる重みなし無向グラフで考える。
まず $X, Y$ は連結である必要がある。
連結成分に閉路が存在すれば、閉路を回ることですれ違うことができる。
それ以外の方法がないか考えてみる。
実は、次数 $3$ の頂点の周りも $1$ つに退避させることですれ違うことができる。
閉路がなくて次数 $2$ 以下ならばパスグラフなので、すれ違えない。
オンラインで、重み $K$ 以下の辺のみを使ったときの
- 連結性
- 連結成分内の閉路の存在
- 連結成分内の次数 $3$ 以上の存在
を判定したい。
連結性が必要なので、重み順に辺を追加して部分永続 UnionFind するのが良いだろう。
閉路と次数の条件も、 UnionFind に追加情報を持たせればできる。 (部分永続なので条件を満たした時間)
計算量は $O(M \log (N + M) + Q \log N \log M)$
オンラインでなければ、 Parallel Binary Search で $\log$ が $1$ 個になる。
## 実装
一般的な部分永続 UnionFind では `root` が `t < time` で時刻 $t$ の直後の根を返すのに対し、時刻 $t$ の直後に条件を満たしているかどうか調べるには `time <= t` になる。
https://oj.uz/submission/365005
```cpp
#include <bits/stdc++.h>
using namespace std;
struct UnionFind{
vector<vector<pair<int, int>>> data;
vector<int> flag_time, degree;
UnionFind(int n = 0): data(n, {{-1, -1}}), flag_time(n, INT_MAX), degree(n){}
int merged_time(int x) const {
const auto [time, root] = data[x].back();
if(root < 0) return INT_MAX;
return time;
}
int root(int t, int x) const { // after t
if(t < merged_time(x)) return x;
return root(t, data[x].back().second);
}
bool unite(int t, int x, int y){
bool flag = ++degree[x] >= 3 || ++degree[y] >= 3;
x = root(t, x);
y = root(t, y);
if(x == y){
if(flag_time[x] == INT_MAX) flag_time[x] = t;
return 0;
}
const int size_x = data[x].back().second, size_y = data[y].back().second;
if(size_x > size_y) swap(x, y);
if(flag_time[x] == INT_MAX && (flag || flag_time[y] != INT_MAX)) flag_time[x] = t;
data[x].emplace_back(t, size_x + size_y);
data[y].emplace_back(t, x);
return 1;
}
} uf;
vector<array<int, 3>> edge;
void init(int N, int M, vector<int> U, vector<int> V, vector<int> W){
uf = UnionFind(N);
edge.resize(M);
for(int i = 0; i < M; i++) edge[i] = {U[i], V[i], W[i]};
sort(edge.begin(), edge.end(), [&](const auto& a, const auto& b){ return a[2] < b[2]; });
for(int i = 0; i < M; i++){
const auto& [U, V, W] = edge[i];
uf.unite(i + 1, U, V);
}
}
int getMinimumFuelCapacity(int X, int Y){
auto check = [&](int t){
const int x = uf.root(t, X), y = uf.root(t, Y);
return x == y && uf.flag_time[x] <= t;
};
int ng = 0, ok = edge.size();
if(!check(ok)) return -1;
while(ok - ng > 1){
const int cen = (ok + ng) / 2;
(check(cen) ? ok : ng) = cen;
}
return edge[ok - 1][2];
}
```