凸多角形の加法 (AOJ 1639)
この記事は帰ってきた AOJ-ICPC Advent Calendar 2022 3 日目の記事です。
ミンコフスキー和に関しての問題です。
問題概要
凸多角形 と整数 があり、 を満たしている。 凸多角形 が与えられるので、条件を満たす のうち、 の面積の和の二倍の最小値を求めてください。 ただし + はミンコフスキー和を表す。
解法
まず、ミンコフスキー和は、凸多角形をなす辺を順番に並べて偏角ソートしたものをマージソートすることで求まることが知られています。 このことから、 のうち、 が分かっているとき を、つまり を求めることができます。
ここで、 を考えてみましょう。線形性より、 が存在したとすると が成り立ちます。 このとき、引き算した部分が打ち消し合うことから、 としたとき、 が引き続き成り立ちます。よって、 で問題を考えても答えは等しくなります。
引き算が出来る条件を考えましょう。これはそこまで難しくなく、単に の全ての辺に対して、同じ向きで長さが のそれ以上であるような辺が に存在していることです。 引き算する向きは一意に定まります。
このユークリッドの互除法のような操作を繰り返すことで、最終的に引き算が行えなくなったとき、それらが です。 これは、引き算が行えないことと かつ (またはその逆) が同値であることと、それに加えて が同時に成り立つことがないことから言えます。( となってしまう)
よって、この操作が行えればこの問題が解けたことが分かりました。 計算量については、ユークリッドの互除法と同様に座標が 2 回で半分以下になることから、 です。(引き算をする際、一気に引ける限り引かないと計算量が になりますが、テストケースが弱いので通ります。(一辺 1 の正方形と、その辺と平行な長さ 10 ^ 6 の辺を含んで頂点数 1000 の図形の組とかがたくさんあると死ぬ)
実装
#include <bits/stdc++.h> using namespace std; #define ll long long #define rep(i, n) for(ll i = 0; i < (n); i++) using P = vector<pair<ll, ll>>; constexpr int type(ll x, ll y) { if(x == 0 and y == 0) return 0; if(y < 0 or (y == 0 and x > 0)) return -1; return 1; } bool arg_cmp(pair<ll, ll> x, pair<ll, ll> y) { int L = type(x.first, x.second), R = type(y.first, y.second); if(L != R) return L < R; ll X = x.first * y.second, Y = y.first * x.second; if(X != Y) return X > Y; return x.first < y.first; } long long solve(P &x, P &y, int second = 0) { bool flag = true; int j = 0; P ny; if(empty(x)) flag = false; for(auto &[a, b] : x) { while(j < y.size()) { auto &[c, d] = y[j]; if(a * d == b * c and abs(a + b) <= abs(c + d)) { break; } ny.emplace_back(c, d); j++; } if(j == y.size()) { flag = false; break; } if(y[j].first - a or y[j].second - b) ny.emplace_back(y[j].first - a, y[j].second - b); j++; } while(j < y.size()) { ny.emplace_back(y[j++]); } if(flag) return solve(x, ny); else if(!second) { return solve(y, x, true); } ll res = 0; auto calc = [&](const P &x) { pair<ll, ll> p{}; rep(i, x.size()) { pair<ll, ll> np(p.first + x[i].first, p.second + x[i].second); res += p.first * np.second - p.second * np.first; p = np; } }; calc(x); calc(y); return res; } int main() { while(true) { int n, m; cin >> n >> m; if(!n) exit(0); auto get = [](int n) { P r(n); rep(i, n) cin >> r[i].first >> r[i].second; P res(n); rep(i, n) res[i] = pair(r[(i + 1) % n].first - r[i].first, r[(i + 1) % n].second - r[i].second); int k = min_element(begin(res), end(res), arg_cmp) - begin(res); rotate(begin(res), begin(res) + k, end(res)); return pair(r, res); }; auto [r, x] = get(n); auto [s, y] = get(m); ll res = solve(x, y); cout << res << endl; } }