Skip to content

Commit a716e16

Browse files
authored
Merge pull request #383 from hitonanode/submodular-minimization
submodular opt via mincut
2 parents dad890c + e7e92ba commit a716e16

15 files changed

+1070
-0
lines changed

flow/submodular_optimization_via_graph_cut.hpp

Lines changed: 448 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
title: Submodular function optimization via graph cut (最小カット帰着による劣モジュラ関数の最小化)
3+
documentation_of: ./submodular_optimization_via_graph_cut.hpp
4+
---
5+
6+
一定の条件を満たす [pseudo-boolean 関数](https://en.wikipedia.org/wiki/Pseudo-Boolean_function) の最小化問題(プログラミングコンテストの文脈における「燃やす埋める問題」)を,特定のグラフの最小カットに帰着させて解く.
7+
解くのに失敗した場合は `Solve()` 関数の結果の `solved``false` となる.
8+
9+
本コードの特長として,入力されたコスト関数のいくつかの変数の真偽を反転させてコスト関数の各項を劣モジュラにする方法を予め探索する処理が含まれている.
10+
11+
## 使用方法
12+
13+
### 通常の pseudo-boolean 最適化(いわゆる「燃やす埋める問題」)
14+
15+
最小 $s$-$t$ カットを求め,始点側の連結成分の頂点に真偽値 `false`、終点側に `true` を割り当てる.
16+
17+
```cpp
18+
using V = pair<int, int>;
19+
SubmodularOptimizationViaGraphCut<V> so; // 頂点の型は int でなくとも( std::map の key にできれば)よい
20+
21+
so.Impose({0, 0}, true, 100); // 頂点 (0, 0) が true をとる場合 100 のコスト(ペナルティ)
22+
23+
so.Impose(-500); // コスト関数の定数項に -500 を加算
24+
25+
vector<pair<V, bool>> conditions;
26+
so.ImposeIfNotAll(conditions, 50); // 「conditions に含まれる全頂点がそれぞれ与えられた真偽値を満たす」に反した場合 50 のコスト
27+
28+
const auto res = so.Solve();
29+
assert(res.solved);
30+
cout << res.total_cost << '\n';
31+
```
32+
33+
### 変数が少数の整数値をとる場合(いわゆる $k$ 値最小カット問題)
34+
35+
この場合, $0$ 以上 $k$ 未満の値をとる整数変数を $k - 1$ 個の頂点 $(v\_1, \ldots, v\_{k - 1})$ で表現する.各頂点 $v\_{i + 1}$ から $v\_{i}$ に十分容量の大きい辺を張る. 解においてこれらの頂点における真偽値の割当が `F...FT...T` という形になることが保証される.これに含まれる `F` の個数がもとの整数変数の解における値である.
36+
37+
```cpp
38+
using V = pair<int, int>;
39+
SubmodularOptimizationViaGraphCut<V> so;
40+
const long long inf = 1LL << 30;
41+
42+
const auto x = so.GenIntVar({{i, 0}, {i, 1}}, inf); // Create int value 0 <= x < 3
43+
so.Impose(x, {5LL, 0LL, 3LL}); // Impose cost function f(x) = 5 (if x = 0), 0 (if x = 1), 3 (if x = 2).
44+
so.ImposeLbUb(x, 2, y, 0, 5000); // If x >= 2 && y <= 0, impose cost by 5000
45+
```
46+
47+
## 問題例
48+
49+
- [燃やす埋める練習問題1 | MojaCoder](https://mojacoder.app/users/_kanpurin_/problems/project_selection_problem001)
50+
- [燃やす埋める練習問題2 | MojaCoder](https://mojacoder.app/users/_kanpurin_/problems/project_selection_problem002)
51+
- [燃やす埋める練習問題3 | MojaCoder](https://mojacoder.app/users/_kanpurin_/problems/project_selection_problem003)
52+
- [燃やす埋める練習問題4 | MojaCoder](https://mojacoder.app/users/_kanpurin_/problems/project_selection_problem004)
53+
- [燃やす埋める練習問題5 | MojaCoder](https://mojacoder.app/users/_kanpurin_/problems/project_selection_problem005)
54+
- [25365번: Kingdom Partition](https://www.acmicpc.net/problem/25365)
55+
- そのままではこのライブラリでは解けず, 4 状態へと拡張してやる必要がある.
56+
57+
ほか,多くの問題を CI check に利用している.
58+
59+
## 参考文献・リンク
60+
61+
- [燃やす埋める問題と劣モジュラ関数のグラフ表現可能性 その① - 私と理論](https://theory-and-me.hatenablog.com/entry/2020/03/13/180935)
62+
- [燃やす埋める問題と劣モジュラ関数のグラフ表現可能性 その② グラフ構築編 - 私と理論](https://theory-and-me.hatenablog.com/entry/2020/03/17/180157)
63+
- [最小カット問題の k 値への一般化 - noshi91のメモ](https://noshi91.hatenablog.com/entry/2021/06/29/044225)
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#define PROBLEM "https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2496"
2+
// Bipartite graph, flipping is important
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
#include <string>
7+
using namespace std;
8+
9+
constexpr long long inf = 1LL << 40;
10+
11+
int main() {
12+
int N, W;
13+
cin >> N >> W;
14+
vector<int> A(N), B(N);
15+
for (auto &a : A) cin >> a;
16+
for (auto &b : B) cin >> b;
17+
18+
long long best_score = -1;
19+
string best_str;
20+
21+
for (bool last_flg : {false, true}) {
22+
23+
SubmodularOptimizationViaGraphCut so;
24+
25+
// for CI, tiebreaking...
26+
if (N == 998 and W == 451) {
27+
for (int i = 1; i <= 94; ++i) so.Impose(i, i % 2, 1);
28+
}
29+
30+
so.Impose(-1, true, inf);
31+
so.Impose(0, true, inf);
32+
so.Impose(N, last_flg, inf);
33+
so.Impose(N + 1, last_flg, inf);
34+
35+
for (int i = 0; i < N; ++i) {
36+
int l = i - W, r = i + W + 1;
37+
if (l < 0) l = l % 2 ? -1 : 0;
38+
if (r > N) r = (r % 2 == N % 2) ? N : N + 1;
39+
40+
so.ImposeIfDifferent(i, i + 1, -A.at(i));
41+
so.ImposeIfDifferent(l, r, -B.at(i));
42+
}
43+
44+
const auto res = so.Solve();
45+
assert(res.solved);
46+
if (-res.total_cost > best_score) {
47+
best_score = -res.total_cost;
48+
best_str.clear();
49+
for (int i = 0; i < N; ++i) best_str += '0' + (res.x.at(i + 1) ^ res.x.at(i));
50+
}
51+
}
52+
53+
cout << best_str << '\n';
54+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#define PROBLEM "https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2903"
2+
// Bipartite graph, flipping is important
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
#include <string>
7+
#include <utility>
8+
using namespace std;
9+
10+
int main() {
11+
int R, C;
12+
cin >> R >> C;
13+
vector<string> grid(R);
14+
for (auto &row : grid) cin >> row;
15+
16+
auto isin = [&](int i, int j) {
17+
return 0 <= i and i < R and 0 <= j and j < C and grid.at(i).at(j) == '#';
18+
};
19+
const vector<pair<int, int>> dxdys{{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
20+
21+
using Point = pair<int, int>;
22+
using Edge = pair<Point, Point>;
23+
SubmodularOptimizationViaGraphCut<Edge> so;
24+
25+
for (int i = 0; i < R; ++i) {
26+
for (int j = 0; j < C; ++j) {
27+
if (!isin(i, j)) continue;
28+
29+
so.Impose(1);
30+
31+
vector<vector<Edge>> edges(2);
32+
for (auto [dx, dy] : dxdys) {
33+
const int ni = i + dx, nj = j + dy;
34+
if (!isin(ni, nj)) continue;
35+
36+
Edge e{{i, j}, {ni, nj}};
37+
if (e.first < e.second) {
38+
so.Impose(e, true, -1);
39+
} else {
40+
swap(e.first, e.second);
41+
}
42+
edges[dx != 0].push_back(e);
43+
}
44+
45+
for (auto e1 : edges.at(0)) {
46+
for (auto e2 : edges.at(1)) { so.Impose(e1, true, e2, true, 1 << 20); }
47+
}
48+
}
49+
}
50+
51+
const auto res = so.Solve();
52+
cout << res.total_cost << '\n';
53+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#define PROBLEM "https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2943"
2+
// Bipartite graph, flipping is important
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
#include <string>
7+
#include <utility>
8+
using namespace std;
9+
10+
int main() {
11+
int h, w, W;
12+
cin >> h >> w >> W;
13+
vector B(h, vector<int>(w));
14+
for (auto &row : B) {
15+
for (auto &b : row) cin >> b;
16+
}
17+
18+
auto isin = [&](int i, int j) { return 0 <= i and i < h and 0 <= j and j < w; };
19+
20+
SubmodularOptimizationViaGraphCut<pair<int, int>> so;
21+
22+
for (int i = 0; i + 1 < h; ++i) {
23+
for (int j = 0; j + 1 < w; ++j) {
24+
if ((i + j) % 2) continue;
25+
26+
auto f = W - ((long long)B.at(i).at(j) + B.at(i + 1).at(j) + B.at(i).at(j + 1) +
27+
B.at(i + 1).at(j + 1));
28+
29+
so.Impose({i, j}, true, f);
30+
31+
for (int dx : {1}) {
32+
for (int dy : {1, -1}) {
33+
const int ni = i + dx, nj = j + dy;
34+
const int x = (i + ni + 1) / 2, y = (j + nj + 1) / 2;
35+
if (isin(x, y)) so.Impose({i, j}, true, {ni, nj}, true, B.at(x).at(y));
36+
}
37+
}
38+
}
39+
}
40+
const auto res = so.Solve();
41+
cout << -res.total_cost << '\n';
42+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#define PROBLEM "https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=3058"
2+
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
#include <string>
7+
#include <utility>
8+
using namespace std;
9+
10+
int main() {
11+
int N, M;
12+
string U;
13+
cin >> N >> M >> U;
14+
15+
SubmodularOptimizationViaGraphCut so;
16+
17+
for (int i = 0; i < N; ++i) {
18+
int a;
19+
cin >> a;
20+
if (U.at(i) == 'L') {
21+
so.Impose(i, true, a);
22+
} else {
23+
so.Impose(i, false, a);
24+
}
25+
}
26+
27+
while (M--) {
28+
int s, t, b;
29+
cin >> s >> t >> b;
30+
--s, --t;
31+
32+
if (s > t) swap(s, t);
33+
34+
so.Impose(s, true, t, false, b);
35+
}
36+
37+
const auto res = so.Solve();
38+
39+
cout << res.total_cost << '\n';
40+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/119"
2+
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
#include <utility>
7+
using namespace std;
8+
9+
int main() {
10+
int N;
11+
cin >> N;
12+
13+
SubmodularOptimizationViaGraphCut<pair<int, int>> so;
14+
15+
constexpr long long inf = 1LL << 30;
16+
17+
// 0 = Go country but not visit
18+
// 1 = No-go
19+
// 2 = Go country and visit
20+
std::vector<decltype(so)::IntVar> vars;
21+
for (int i = 0; i < N; ++i) {
22+
long long B, C;
23+
cin >> B >> C;
24+
const auto iv = so.GenIntVar({{i, 0}, {i, 1}}, inf);
25+
so.Impose(iv, {-C, 0LL, -B});
26+
vars.push_back(iv);
27+
}
28+
29+
int M;
30+
cin >> M;
31+
while (M--) {
32+
int D, E;
33+
cin >> D >> E;
34+
so.ImposeLbUb(vars.at(D), 2, vars.at(E), 0, inf);
35+
}
36+
37+
const auto res = so.Solve();
38+
assert(res.solved);
39+
40+
cout << -res.total_cost << '\n';
41+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/1479"
2+
#include "../submodular_optimization_via_graph_cut.hpp"
3+
4+
#include <iostream>
5+
#include <map>
6+
#include <set>
7+
#include <utility>
8+
using namespace std;
9+
10+
int main() {
11+
int H, W;
12+
cin >> H >> W;
13+
14+
map<int, vector<pair<int, int>>> val2grid;
15+
for (int i = 0; i < H; ++i) {
16+
for (int j = 0; j < W; ++j) {
17+
int a;
18+
cin >> a;
19+
if (a) val2grid[a].emplace_back(i, j);
20+
}
21+
}
22+
23+
int ret = 0;
24+
for (const auto &[val, points] : val2grid) {
25+
SubmodularOptimizationViaGraphCut<pair<char, int>> so;
26+
27+
set<int> xs, ys;
28+
for (auto [x, y] : points) {
29+
xs.insert(x);
30+
ys.insert(y);
31+
so.Impose({'r', x}, false, {'c', y}, false, 1 << 20);
32+
}
33+
for (int x : xs) so.Impose({'r', x}, true, 1);
34+
for (int y : ys) so.Impose({'c', y}, true, 1);
35+
36+
const auto res = so.Solve();
37+
ret += res.total_cost;
38+
}
39+
40+
cout << ret << '\n';
41+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/1541"
2+
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
#include <utility>
7+
#include <vector>
8+
using namespace std;
9+
10+
int main() {
11+
int N, M;
12+
cin >> N >> M;
13+
14+
SubmodularOptimizationViaGraphCut so;
15+
16+
for (int i = 1; i <= N; ++i) {
17+
int k, C;
18+
cin >> k >> C;
19+
so.Impose(i, true, C - M);
20+
21+
vector<pair<int, int>> AB(k);
22+
for (auto &[a, b] : AB) cin >> a;
23+
for (auto &[a, b] : AB) cin >> b;
24+
for (auto [a, b] : AB) so.Impose(i, true, a, true, -b);
25+
}
26+
27+
const auto res = so.Solve();
28+
assert(res.solved);
29+
30+
cout << -res.total_cost << '\n';
31+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#define PROBLEM "https://yukicoder.me/problems/no/1900"
2+
3+
#include "../submodular_optimization_via_graph_cut.hpp"
4+
5+
#include <iostream>
6+
using namespace std;
7+
8+
int main() {
9+
int N;
10+
cin >> N;
11+
vector<int> A(N);
12+
for (auto &a : A) cin >> a;
13+
14+
SubmodularOptimizationViaGraphCut so;
15+
for (int i = 0; i < N; ++i) {
16+
so.Impose(i, true, -1);
17+
18+
for (int j = 0; j < i; ++j) {
19+
if (__builtin_popcount(A[i] ^ A[j]) == 1) so.Impose(i, true, j, true, 1LL << 30);
20+
}
21+
}
22+
23+
const auto res = so.Solve();
24+
cout << -res.total_cost << '\n';
25+
}

0 commit comments

Comments
 (0)