This documentation is automatically generated by online-judge-tools/verification-helper
#include "combinatorial_opt/matroid_union.hpp"2つのマトロイド $M_{1} = (E, \mathcal{I}_{1}), M_{2} = (E, \mathcal{I}_{2})$, $\mathcal{I}_{1}$ に関して独立な集合 $I_1$, $\mathcal{I}_{2}$ に関して独立な集合 $I_2$ で $I_1 \cap I_2 = \emptyset$ を満たすものが与えられたとき,$I’_1 + I’_2 = I_1 + I_2 + \{ e \}$ を満たす新たな排反な独立集合 $I’_1, I’_2$ を見つけるアルゴリズム.特に重み最小の $e$ から貪欲に追加を試すことで,「合併したマトロイド」の最小重みサイズ $k$ 独立集合が $k = 1, 2, \dots$ について順次求められる.
これを応用すると,与えられた重み付き無向グラフについて,辺素な二つの全域木であって重みの総和が最小なものを見つけることも可能.
$|E| = n$ として,各要素 $e \in E$ に終点 $t$ を加えた $n + 1$ 頂点の有向グラフに次の要領で辺を張る.すべての辺は重みなしとする.
辺を逆向きにしたグラフで $t$ から BFS を行い,各要素から $t$ への最短路を求める.$e \notin I_1 \cup I_2$ で $t$ に到達可能な要素のうち,重み $w(e)$ が最小のものを追加する.このとき重みと交換回数は足し合わせず,まず追加要素の重みを最小化し,その要素からの交換回数を最小化する.該当する要素がなければ $I_1 \cup I_2$ は既に合併したマトロイド上の最大独立集合である.
選んだ $e$ から $t$ への最短路上で,要素を $I_1$ と $I_2$ の間で移動させる.最短路を使うことで独立性が保たれ,合併集合には $e$ だけが新たに加わる.重みを省略した場合は,追加可能ないずれかの要素を選ぶ.
本コードには実装されていないが,3 個以上のマトロイドの合併に対しても同様のアルゴリズムが適用可能である.
bool augment_union_matroid(matroid1, matroid2, I1, I2, weight) 関数は,上述の追加可能な重み最小要素 $e$ を見つけ,$I_1$ および $I_2$ を追加後の状態に更新する.要素の追加が行われたならば true を,そうでなければ false を返す.pair<vector<bool>, vector<bool>> MinWeightMaxIndepSetInUnionMatroid(matroid1, matroid2, weight) 関数は,augment_union_matroid() 関数を繰り返し適用することで,2つのマトロイドを合併したマトロイドにおける(重み最小な)最大独立集合を構成する.augment_union_matroid() を真面目に使用するというヒューリスティックによって 最大 200 ms 程度の実行時間で用意された全ケースに通る(ただし,後述するように無向グラフの全域森への分割にはより高速なアルゴリズムが存在する:edge_disjoint_min_spanning_forests.hpp 参照).#pragma once
#include <cassert>
#include <utility>
#include <vector>
// CUT begin
// Union matroid augment
// From I1, I2 (independent, exclusive),
// - find I1' and I2' s.t. |I1'| + |I2'| = |I1| + |I2| + 1 and return true
// - or return false
// Complexity: O(n(c + r)) (r: current rank, c: circuit query)
// Algorithm based on https://math.mit.edu/~goemans/18438F09/lec13.pdf
// Verified: CodeChef HAMEL
template <class M1, class M2, class State1, class State2, class T = int>
bool augment_union_matroid(M1 &matroid1, M2 &matroid2, State1 &I1, State2 &I2,
const std::vector<T> &weights) {
const int M = matroid1.size();
const int gt = M;
std::vector<std::vector<int>> rev(M + 1);
std::vector<int> color(M, -1);
matroid1.set(I1);
matroid2.set(I2);
for (int e = 0; e < M; e++) {
if (!I1[e]) {
auto c = matroid1.circuit(e);
if (c.empty()) rev[gt].push_back(e), color[e] = 0;
for (int f : c) {
if (f != e) rev[f].push_back(e);
}
}
if (!I2[e]) {
auto c = matroid2.circuit(e);
if (c.empty()) rev[gt].push_back(e), color[e] = 1;
for (int f : c) {
if (f != e) rev[f].push_back(e);
}
}
}
// Find a shortest exchange path to gt from every augmentable element.
std::vector<int> next(M + 1, -1), q{gt};
next[gt] = gt;
for (int i = 0; i < int(q.size()); ++i) {
for (int e : rev[q[i]]) {
if (next[e] >= 0) continue;
next[e] = q[i];
q.push_back(e);
}
}
// Minimize the added weight independently of the number of exchanges.
int start = -1;
for (int e = 0; e < M; ++e) {
if (I1[e] or I2[e] or next[e] < 0) continue;
if (start < 0 or (!weights.empty() and weights[e] < weights[start])) start = e;
}
if (start < 0) return false;
std::vector<int> aug_path;
for (int e = start; e != gt; e = next[e]) aug_path.push_back(e);
int c0 = -1;
if (I1[aug_path.back()]) c0 = 1;
if (I2[aug_path.back()]) c0 = 0;
if (c0 < 0) c0 = color[aug_path.back()];
for (int k = int(aug_path.size()) - 1; k >= 0; --k) {
int e = aug_path[k];
(c0 ? I2 : I1)[e] = 1, (c0 ? I1 : I2)[e] = 0;
c0 ^= 1;
}
return true;
}
// (Min weight) matroid partition
template <class M1, class M2, class T = int>
std::pair<std::vector<bool>, std::vector<bool>>
MinWeightMaxIndepSetInUnionMatroid(M1 mat1, M2 mat2, const std::vector<T> &weights = {}) {
using State = std::vector<bool>;
assert(mat1.size() == mat2.size());
const int M = mat1.size();
State I1(M), I2(M);
while (augment_union_matroid(mat1, mat2, I1, I2, weights)) {}
return {I1, I2};
}#line 2 "combinatorial_opt/matroid_union.hpp"
#include <cassert>
#include <utility>
#include <vector>
// CUT begin
// Union matroid augment
// From I1, I2 (independent, exclusive),
// - find I1' and I2' s.t. |I1'| + |I2'| = |I1| + |I2| + 1 and return true
// - or return false
// Complexity: O(n(c + r)) (r: current rank, c: circuit query)
// Algorithm based on https://math.mit.edu/~goemans/18438F09/lec13.pdf
// Verified: CodeChef HAMEL
template <class M1, class M2, class State1, class State2, class T = int>
bool augment_union_matroid(M1 &matroid1, M2 &matroid2, State1 &I1, State2 &I2,
const std::vector<T> &weights) {
const int M = matroid1.size();
const int gt = M;
std::vector<std::vector<int>> rev(M + 1);
std::vector<int> color(M, -1);
matroid1.set(I1);
matroid2.set(I2);
for (int e = 0; e < M; e++) {
if (!I1[e]) {
auto c = matroid1.circuit(e);
if (c.empty()) rev[gt].push_back(e), color[e] = 0;
for (int f : c) {
if (f != e) rev[f].push_back(e);
}
}
if (!I2[e]) {
auto c = matroid2.circuit(e);
if (c.empty()) rev[gt].push_back(e), color[e] = 1;
for (int f : c) {
if (f != e) rev[f].push_back(e);
}
}
}
// Find a shortest exchange path to gt from every augmentable element.
std::vector<int> next(M + 1, -1), q{gt};
next[gt] = gt;
for (int i = 0; i < int(q.size()); ++i) {
for (int e : rev[q[i]]) {
if (next[e] >= 0) continue;
next[e] = q[i];
q.push_back(e);
}
}
// Minimize the added weight independently of the number of exchanges.
int start = -1;
for (int e = 0; e < M; ++e) {
if (I1[e] or I2[e] or next[e] < 0) continue;
if (start < 0 or (!weights.empty() and weights[e] < weights[start])) start = e;
}
if (start < 0) return false;
std::vector<int> aug_path;
for (int e = start; e != gt; e = next[e]) aug_path.push_back(e);
int c0 = -1;
if (I1[aug_path.back()]) c0 = 1;
if (I2[aug_path.back()]) c0 = 0;
if (c0 < 0) c0 = color[aug_path.back()];
for (int k = int(aug_path.size()) - 1; k >= 0; --k) {
int e = aug_path[k];
(c0 ? I2 : I1)[e] = 1, (c0 ? I1 : I2)[e] = 0;
c0 ^= 1;
}
return true;
}
// (Min weight) matroid partition
template <class M1, class M2, class T = int>
std::pair<std::vector<bool>, std::vector<bool>>
MinWeightMaxIndepSetInUnionMatroid(M1 mat1, M2 mat2, const std::vector<T> &weights = {}) {
using State = std::vector<bool>;
assert(mat1.size() == mat2.size());
const int M = mat1.size();
State I1(M), I2(M);
while (augment_union_matroid(mat1, mat2, I1, I2, weights)) {}
return {I1, I2};
}