誘導部分グラフの数え上げ問題
注意
- グラフは全て単純無向グラフとする。
- グラフ
の頂点数を
、辺数を
と表す。
- 頂点
の次数を
と表す。
- グラフの隣接行列の成分を
と表す。
概要
以下の問題を考える。
頂点
辺のグラフ
が与えられる。
に部分グラフとして含まれる
の個数と
の補グラフ
に部分グラフとして含まれる
の個数の和を求めよ。
この問題は、以下のような考察により 時間で解くことができることが知られている。
相異なる頂点の組
であって、以下の条件を満たすものを考える。
において、
間に辺が存在し、
間に辺が存在しない。
このような組の個数は、
を固定することで簡単に数えられる。具体的には、個数は
と表される。
相異なる
頂点の集合
について、
による
の誘導部分グラフまたは
の誘導部分グラフが
となる場合、個数に
の寄与があり、そうでない場合、個数に
の寄与がある。
よって、求める個数は
である。
に含まれる
の個数を数える通常のアルゴリズムは
時間であり、
時間で簡単に解けるアルゴリズムは知られていない。にもかかわらず、補グラフとの個数の和は簡単に計算できるところが興味深い。本記事では、より一般に、グラフに含まれる特定の誘導部分グラフの個数を数え上げる問題の構造を分析し、このような現象が生じる理由を説明する。
誘導部分グラフ数え上げ問題
グラフ に対し、グラフ
の相異なる
個の頂点の組
であって、それらの頂点による
の誘導部分グラフが
に同型であるものの個数を
と書く。
グラフを引数とする実数値関数 が以下の条件を満たすとき、
は 誘導部分グラフ数え上げ関数 であるという。
- あるグラフ
と実数
が存在し、任意のグラフ
について
と表される。
さらに、 がすべて
頂点のグラフであるとき、
は
頂点誘導部分グラフ数え上げ関数 であるという。
すべての誘導部分グラフ数え上げ関数の集合を とおき、その部分集合である、
頂点誘導部分グラフ数え上げ関数の集合を
とおく。
および
は、実数値関数に関する自然な加法とスカラー倍について
上のベクトル空間をなす。
頂点のグラフが同型を除いて
の
個であるとき、
であるから、
は
を基底とする
次元ベクトル空間となる。
さらに、任意の関数 について、
時間で計算できる誘導部分グラフ数え上げ関数の集合は
の部分空間をなす。なぜならば、いくつかの関数
がそれぞれ
時間で計算できるならば、それらの線形結合も
時間で計算できるからである。
頂点の場合
時間で計算できる誘導部分グラフ数え上げ関数のなす
の部分空間を
とおく。
を特定することが目標である。
頂点のグラフは同型を除いて
つである。辺が
本であるものをそれぞれ
とおく。
の元を、基底
による成分表示で
の元と同一視する。
以下、 と書いたら、暗黙に
は相異なることを課すとする。
とし、
を
なる関数とすると、
は
に属し、その成分表示は
である。
とおくと、
である。よって、
で、その成分表示は
である。
とおくと、
の対称性に注意し、
である。よって、
で、その成分表示は
である。
とおくと、
の対称性に注意し、
である。よって、
で、その成分表示は
である。
を引き
で割ると、
で、その成分表示は
である。
は
次独立であるから、
は少なくとも
次元である。
グラフに含まれる を数え上げる問題、すなわち関数
を計算する問題が
時間で解けないと仮定する。このとき、
であるから、
である。よって、
は
により張られる部分空間に一致する。これは、
とも表される。
例 1
本記事冒頭の問題を解く方法を導出する。 頂点に順序をつけて考え、最後に
で割ればよい。
は
を満たすので、
の元である。よって、
は
で計算できる。
を
の基底
の線形結合で表すと、
となる。よって、
を計算する計算式は
である。これは確かに冒頭で求めた式の
倍と一致する。
例 2
以下の問題を考える。
相異なる頂点
であって、
により誘導される誘導部分グラフがちょうど
本の辺を持つものの個数を求めよ。
を計算すればよい。
より、
である。よって、(上の仮定の下で) この問題は
時間で解くことができず、
の数え上げアルゴリズムが必要である。
は
の基底をなす。
をこれらの線形結合で表すと、
となる。よって、
を計算する計算式は
であり、
時間で計算できる。
yukicoder No. 2530: Yellow Cards
のもとで、
時間で解きます。
解法
[1]
解法
を、
回のイベントの直後に退場となった選手の人数がちょうど
人であるような確率とします。
回のイベントの直後に退場となった選手の人数がちょうど
人であるとき、今までの
回のイエローカードの提示のうち
回は選手を退場させるために使われているので、プレイエリアにいる選手のうちイエローカードを
回提示された選手の人数は
人となります。よって、以下のような漸化式が得られます。
また、初期値は 、答えは
となります。(無限和の形で書いていますが、実際には加える要素数は
個となります。)
[2]
解法
であることに注意します。
の値に関心があるので、この値を
とおきます。
の漸化式から、
この漸化式に従い、 を
から
時間で計算できます。
[3]
解法
の漸化式を解きます。特殊解として
の形を仮定すると、
両辺を係数比較することで を得ます。よって、
したがって、
なので、答えは
となります。
これは 時間で計算できます。
MMA Contest 016 I: regisys?
解法
[1] グリッドの問題への言い換え
各 について、商品
を購入できる一般人の人数を
、商品
を購入できる MMA 部員の人数を
とします。また、一般人の人数に
を加えた値を
、MMA 部員の人数に
を加えた値を
とおくと、以下のような問題に帰着されます。
のグリッドがある。
個の点があり、点
はマス
(0-indexed) にある。
について以下の操作をそれぞれ行う。
座標が
以上の点を
つ選んで取り除く。(以下操作
と呼ぶ)
について以下の操作をそれぞれ行う。
座標が
以上の点を
つ選んで取り除く。
残る点の最小個数を求めよ。(以下操作
と呼ぶ)
[2] Hall の結婚定理の利用
残る点の最小個数を求めるかわりに、最初にいくつかの点を消し、残った点の集合を としたとき、操作により
に属する点をすべて取り除くことが可能であるために消す点の個数の最小値を求めることにします。
Hall の結婚定理により、 に含まれる点をすべて取り除けることは、以下の条件と同値になります。
の任意の部分集合
について、
に属する点を取り除くことが可能な操作の集合を
とおくと、
である。
が空の場合は明らかに成立するので、以下
は空でないとします。
に属する点の
座標の最大値を
、
座標の最大値を
とおくと、
となるので、
です。よって
は
と
にしかよらないので、ある
と
について
に含まれる点をすべて含むような部分集合
のみ考えればよいです。
したがって、 に対し、
に含まれる点の個数を
とおくと、点をすべて消せることは
がすべての
について成り立つことと同値になります。(点が存在しない
または
座標についても考えていますが、点が存在するところまで
を小さくした条件から従います。) また、
とおきます。
[3]
の性質
は anti-monge になる、つまり、
に対し、
が成り立つことを示します。
定義から、 です。これは領域
に含まれる点の個数となるので
以上です。
消す点の個数の最小値は、実は の最大値に等しくなります。これを帰納法により示します。
の最大値は
以上である。
の最大値が
の場合、点を消さなくても操作により点をすべて取り除ける。
の最大値が
であるとする。
となるような
のうち、
の最小値を
、
の最小値を
とおくと、
であることを示す。ある
であって
であるようなものが存在する。
または
の場合は自明なのでそうでないとすると、B の anti-monge 性より
で、
の最大値は
より
となる。
より、
の範囲に
個の点が存在する。このうち
つを選んで消すと、
の最大値が
の場合に帰着する。また、それ以外の点を消すと
の最大値は
のままであるから、消す点の個数の最小値は
個である。
実装例
#include <bits/stdc++.h> using namespace std; const int INF = 10000000; template <typename T> struct lazy_segment_tree{ int N; vector<T> ST; vector<T> lazy; lazy_segment_tree(vector<int> &A){ int n = A.size(); N = 1; while (N < n){ N *= 2; } ST = vector<T>(N * 2 - 1, -INF); lazy = vector<T>(N * 2 - 1, 0); for (int i = 0; i < n; i++){ ST[N - 1 + i] = A[i]; } for (int i = N - 2; i >= 0; i--){ ST[i] = max(ST[i * 2 + 1], ST[i * 2 + 2]); } } void eval(int i){ if (i < N - 1){ lazy[i * 2 + 1] += lazy[i]; lazy[i * 2 + 2] += lazy[i]; } ST[i] += lazy[i]; lazy[i] = 0; } void range_add(int L, int R, T x, int i, int l, int r){ eval(i); if (R <= l || r <= L){ return; } else if (L <= l && r <= R){ lazy[i] += x; eval(i); } else { int m = (l + r) / 2; range_add(L, R, x, i * 2 + 1, l, m); range_add(L, R, x, i * 2 + 2, m, r); ST[i] = max(ST[i * 2 + 1], ST[i * 2 + 2]); } } void range_add(int L, int R, T x){ range_add(L, R, x, 0, 0, N); } T all(){ eval(0); return ST[0]; } }; int main(){ int N, M; cin >> N >> M; vector<int> A(N); for (int i = 0; i < N; i++){ cin >> A[i]; } vector<int> B(N); for (int i = 0; i < N; i++){ cin >> B[i]; } vector<int> T(M), C(M); for (int i = 0; i < M; i++){ cin >> T[i] >> C[i]; } vector<vector<int>> C2(2); for (int i = 0; i < M; i++){ C2[T[i]].push_back(C[i]); } for (int i = 0; i < 2; i++){ sort(C2[i].begin(), C2[i].end()); } int H = C2[0].size() + 1; int W = C2[1].size() + 1; vector<int> x(N), y(N); for (int i = 0; i < N; i++){ x[i] = C2[0].end() - lower_bound(C2[0].begin(), C2[0].end(), A[i]); y[i] = C2[1].end() - lower_bound(C2[1].begin(), C2[1].end(), B[i]); } vector<vector<int>> add(H); for (int i = 0; i < N; i++){ add[x[i]].push_back(y[i]); } vector<int> a(W); for (int i = 0; i < W; i++){ a[i] = -i; } lazy_segment_tree<int> ST(a); int ans = 0; for (int i = 0; i < H; i++){ for (int j : add[i]){ ST.range_add(j, W, 1); } ans = max(ans, ST.all()); ST.range_add(0, W, -1); } cout << ans << endl; }
AGC060 C: Large Heap
解法
[1] 条件の言い換え
対称性により、不等号の向きをすべて反転させても求める確率は変わりません。後の計算がやりやすくなるので、不等号の向きをすべて反転させて考えます。
長さ の実数列
が以下の条件を満たすとき、
は
上の列であると呼ぶことにします。
上の列
がさらに以下の条件をすべて満たすとき、
は
上のヒープ的な列であると呼ぶことにします。
このとき、求める確率は 上のヒープ的な列
を一様ランダムに
つ選んだときに
となる確率に一致します。これは以下のようにしてわかります。
上の列
がヒープ的であるか、また
であるかは、
の間の大小関係のみに依存する。
上の列を一様ランダムに
つ選んだとき、等しい要素が存在する確率は
であるから無視できる。また、各要素が相異なるとき、要素間の大小関係としてありうるものは
通りあるが、それらは等確率で現れる。
さらに、以下のような根付き木 を考えます。
個の頂点を持つ。
- 根は頂点
である。
について、頂点
は頂点
の
つの子を持つ。
このとき、 上の列
がヒープ的であることは、木
の各頂点
に
を書いたとき、頂点
が頂点
の親であるような木
の頂点の組
すべてについて頂点
に書かれた値が頂点
に書かれた値より大きいことと同値になります。
以上により、順列に関する問題を、木の各頂点に区間 に含まれる実数を書く問題に言い換えることができました。
上の列
を一様ランダムに選んだとき、
がヒープ的である確率を
,
がヒープ的でかつ
である確率を
とおくと、答えは
となります。よって、
をそれぞれ求めればよいです。
[2]
の計算
を求めるときは、実数で考えるのではなく、順列のままで考えると求めやすいです。
以下の事実が典型として知られています。
頂点の根付き木がある。長さ
の順列
を一様ランダムに
つ選んだとき、根以外の全ての頂点
に対し、その親を
とすると
となる確率は、頂点
の部分木の頂点数を
とおくと
である。
木 は深さ
の完全二分木です。
に対し、深さ
の頂点は
個存在し、それらの頂点の部分木の頂点数はすべて
です。したがって、
となります。
もしくは、最終的に用いる値が であることから、漸化式
を用いると簡単に計算することができます。
[3]
の計算
頂点 に書く値については特別な制約があるため、最初に固定しておきます。具体的には、頂点
には必ず
を、頂点
には必ず
を書くことにします。ただし、
とします。
以下のような木 DP を考えます。
: 木
の頂点
の部分木の各頂点に区間
に含まれる実数を独立に一様ランダムに書くが、頂点
に数を書く場合には必ず
を書き、頂点
に数を書く場合には必ず
を書くとき、頂点
に書いた数が
以下であり、かつ頂点
の部分木で大小関係の条件が満たされている確率
ただし、頂点 の部分木で大小関係の条件が満たされているとは、頂点
の部分木に含まれる各頂点
に対し頂点
に書いた数が頂点
の子に書いた数より大きくなることと定義します。また、
は
の範囲のみ考えるとします。
DP の遷移を考えます。
頂点
が葉で、
または
である場合
頂点 が葉なので、大小関係に関する制約はありません。
のとき、頂点
に書かれる数は必ず
なので、頂点
に書かれる数が
以下になる確率は
のとき
,
のとき
となります。よって、
となります。
同様に、 のときは
となります。
頂点
が葉で、
でも
でもない場合
上と同じく、大小関係に関する制約はないので、頂点 に書かれる数は区間
から一様ランダムに選ばれます。よって、頂点
に書かれる数が
以下になる確率は
となるので、
となります。
頂点
が葉でなく、
または
である場合
のとき、頂点
に書かれる数は必ず
です。よって、
のとき明らかに
となります。
とします。頂点
の部分木で大小関係の条件が満たされることは、以下の条件がともに成り立つことと同値になります。
- 頂点
に書かれた数が
以下であり、かつ頂点
の部分木で大小関係の条件が満たされている。
- 頂点
に書かれた数が
以下であり、かつ頂点
の部分木で大小関係の条件が満たされている。
よって、頂点 の部分木で大小関係の条件が満たされる確率は
です。
以上より、遷移は となります。
同様に、 の場合の遷移は
となります。
頂点
が葉でなく、
でも
でもない場合
頂点 に書く数を
とします。頂点
の部分木で大小関係の条件が満たされることは、以下の条件がともに成り立つことと同値になります。
- 頂点
に書かれた数が
以下であり、かつ頂点
の部分木で大小関係の条件が満たされている。
- 頂点
に書かれた数が
以下であり、かつ頂点
の部分木で大小関係の条件が満たされている。
これが成り立つ確率は です。
は区間
から一様ランダムに選ばれますが、
である必要があるので、遷移は
となります。
は、木
の頂点
に
を、頂点
に頂点
を、他の各頂点に区間
に含まれる実数を独立に一様ランダムに選んで書くとき、各辺の大小関係の条件が満たされる確率となります。よって、
となります。
[4] 計算例
例として、サンプル の結果を計算する過程を示します。
である。
- 頂点
は葉で頂点
であるので、
である。以後、このような場合に
と省略する。
- 頂点
は葉で頂点
でも頂点
でもないので、
である。頂点
も同様である。
である。
である。頂点
も同様である。
である。
である。
である。
である。
である。これを計算すると
となる。
- 答えは
である。
[5] 計算量の削減
頂点は 個あるため、すべての頂点について
を計算することはできません。しかし、木
には構造が同じ部分が多数存在することを利用すると、
を計算する必要のある頂点を
個に減らすことができます。各頂点の
は
の多項式となりますが、それらの特徴と計算手順は以下のようになります。ただし、
とします。
を
の先祖でも
の先祖でもない頂点とすると、
は頂点
の高さのみに依存する
のみの単項式である。
は
時間の前計算のもと、
時間で得ることができる。
を
の先祖で
の先祖でない頂点とする。また、頂点
の距離を
とおくと、
は
の
項の多項式であり、次数はすべて等しい。
のとき、
の子で部分木に
を含むほうを
とおくと、
は
から
時間で計算できる。
を
の先祖で
の先祖でない頂点とする。また、頂点
の距離を
とおくと、
は
の
項の多項式であり、次数はすべて等しい。
のとき、
の子で部分木に
を含むほうを
とおくと、
は
から
時間で計算できる。
これらを用いると、 を
時間で計算することができます。また、
は
の
項の多項式、
は
の
項の多項式なので、
は
項の多項式となります。
なので、積分を項ごとに分解すると、
を計算することに帰着されます。
なので、積分の順序を交換して
となります。これで
を
時間で求めることができたので、答えも
時間で求めることができます。
実装
#include <bits/stdc++.h> using namespace std; const long long MOD = 998244353; long long modpow(long long a, long long b){ long long ans = 1; while (b > 0){ if (b % 2 == 1){ ans *= a; ans %= MOD; } a *= a; a %= MOD; b /= 2; } return ans; } long long modinv(long long a){ a %= MOD; return modpow(a, MOD - 2); } vector<long long> POW2; struct poly{ vector<tuple<long long, long long, long long>> P; poly(): P(){ } int size(){ return P.size(); } void multiply_monomial(poly f){ int N = P.size(); for (int i = 0; i < N; i++){ get<0>(P[i]) *= get<0>(f.P[0]); get<0>(P[i]) %= MOD; get<1>(P[i]) += get<1>(f.P[0]); get<1>(P[i]) %= MOD; get<2>(P[i]) += get<2>(f.P[0]); get<2>(P[i]) %= MOD; } } void integrate(){ int N = P.size(); for (int i = 0; i < N; i++){ get<1>(P[i])++; get<1>(P[i]) %= MOD; get<0>(P[i]) *= modinv(get<1>(P[i])); get<0>(P[i]) %= MOD; } } void integrate_a(){ int N = P.size(); long long sum = 0; long long deg = (get<1>(P[0]) + get<2>(P[0]) + 1) % MOD; for (int i = 0; i < N; i++){ get<1>(P[i])++; get<1>(P[i]) %= MOD; get<0>(P[i]) *= modinv(get<1>(P[i])); get<0>(P[i]) %= MOD; sum += MOD - get<0>(P[i]); sum %= MOD; } P.push_back(make_tuple(sum, 0, deg)); } }; int main(){ int N, A, B; cin >> N >> A >> B; POW2.resize(N + 1); POW2[0] = 1; for (int i = 0; i < N; i++){ POW2[i + 1] = POW2[i] * 2 % MOD; } vector<poly> f(N); f[N - 1].P.push_back(make_tuple(1, 1, 0)); for (int i = N - 2; i >= 0; i--){ f[i] = f[i + 1]; f[i].multiply_monomial(f[i]); f[i].integrate(); } poly ga; if (A == N - 1){ ga.P.push_back(make_tuple(1, 0, 0)); } else { ga = f[A + 1]; ga.multiply_monomial(ga); swap(get<1>(ga.P[0]), get<2>(ga.P[0])); } for (int i = A; i >= 2; i--){ ga.multiply_monomial(f[i]); ga.integrate_a(); } poly gb; if (B == N - 1){ gb.P.push_back(make_tuple(1, 0, 0)); } else { gb = f[B + 1]; gb.multiply_monomial(gb); swap(get<1>(gb.P[0]), get<2>(gb.P[0])); } for (int i = B; i >= 2; i--){ gb.multiply_monomial(f[i]); gb.integrate_a(); } int N1 = ga.size(); int N2 = gb.size(); long long p2 = 0; vector<long long> T(N2); for (int i = 0; i < N2; i++){ T[i] = modinv(get<2>(gb.P[i]) + 1); } for (int i = 0; i < N1; i++){ for (int j = 0; j < N2; j++){ long long c = get<0>(ga.P[i]) * get<0>(gb.P[j]) % MOD; long long x = (get<1>(ga.P[i]) + get<1>(gb.P[j])) % MOD; long long a = get<2>(ga.P[i]); long long b = get<2>(gb.P[j]); p2 += T[j] * modinv(a + b + 2) % MOD * modinv(x + a + b + 3) % MOD * c % MOD; p2 %= MOD; } } long long p1inv = 1; for (int i = 2; i <= N; i++){ p1inv *= p1inv; p1inv %= MOD; p1inv *= POW2[i] - 1; p1inv %= MOD; } cout << p2 * p1inv % MOD << endl; }
AOJ 2154: Shore Erosion
この記事は帰ってきた AOJ-ICPC Advent Calendar 2022 22 日目の記事です。
https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2154
解法
与えられた多角形の内部を 、頂点を反時計回りに
とします。また、
であるとします。
辺 からのマンハッタン距離が
以下である点の集合を
とすると、島のある点が浸食される条件は、その点がある
に対し
に含まれることです。よって、浸食された後の島は
となります。
領域 は四角形もしくは六角形になります。具体的には、点
があるとき、線分
からのマンハッタン距離が
以下である領域は以下のように得られます。
と仮定しても一般性を失わないので、そのように仮定する。
の場合、
点
を頂点とする六角形である。
- そうでない場合、
点
を頂点とする六角形である。
Slab Decomposition で平面を の辺で分割し、それぞれの領域が浸食された後の島であるかどうかを調べます。ビット演算を用いて、それぞれの領域がどれに含まれるかを計算すると得られます。その後、島である領域と島でない領域が隣接する部分それぞれについて、長さを足し合わせればよいです。
実装
#include <bits/stdc++.h> using namespace std; const double eps = 0.000001; int sign(double x){ if (x > eps){ return 1; } else if (x < -eps){ return -1; } else { return 0; } } struct point{ double x, y; point(){ } point(double x, double y): x(x), y(y){ } point operator +(point P){ return point(x + P.x, y + P.y); } point operator -(point P){ return point(x - P.x, y - P.y); } point operator *(double k){ return point(x * k, y * k); } point operator /(double k){ return point(x / k, y / k); } }; double abs(point P){ return sqrt(P.x * P.x + P.y * P.y); } double dist(point P, point Q){ return abs(Q - P); } point rotate(point P, double t){ return point(P.x * cos(t) - P.y * sin(t), P.x * sin(t) + P.y * cos(t)); } double cross(point P, point Q){ return P.x * Q.y - P.y * Q.x; } struct line{ point A, B; line(){ } line(point A, point B): A(A), B(B){ } }; point vec(line L){ return L.B - L.A; } bool is_parallel(line L1, line L2){ return abs(cross(vec(L1), vec(L2))) < eps; } bool segment_line_intersect(line L1, line L2){ return sign(cross(L1.A - L2.A, vec(L2))) * sign(cross(L1.B - L2.A, vec(L2))) <= 0; } bool segment_intersect(line L1, line L2){ return segment_line_intersect(L1, L2) && segment_line_intersect(L2, L1); } point line_intersection(line L1, line L2){ return L1.A + vec(L1) * cross(L2.A - L1.A, vec(L2)) / cross(vec(L1), vec(L2)); } vector<double> unique_eps(vector<double> A){ int N = A.size(); sort(A.begin(), A.end()); vector<double> A2; A2.push_back(A[0]); for (int i = 1; i < N; i++){ if (A[i] - A[i - 1] > eps){ A2.push_back(A[i]); } } return A2; } template <typename T> struct slab_decomposition{ function<T(T, T)> f; T e; vector<pair<line, T>> S; vector<double> X; int N; vector<vector<double>> Y; vector<int> P; vector<vector<tuple<int, int, T>>> E; slab_decomposition(function<T(T, T)> f, T e): f(f), e(e){ } void add_segment(line s, T x){ S.push_back(make_pair(s, x)); } vector<vector<pair<T, int>>> build(){ int M = S.size(); for (int i = 0; i < M; i++){ if (S[i].first.A.x > S[i].first.B.x){ swap(S[i].first.A, S[i].first.B); } } for (int i = 0; i < M; i++){ X.push_back(S[i].first.A.x); X.push_back(S[i].first.B.x); } for (int i = 0; i < M; i++){ for (int j = i + 1; j < M; j++){ if (!is_parallel(S[i].first, S[j].first) && segment_intersect(S[i].first, S[j].first)){ X.push_back(line_intersection(S[i].first, S[j].first).x); } } } X = unique_eps(X); N = X.size(); vector<int> L(M), R(M); for (int i = 0; i < M; i++){ L[i] = lower_bound(X.begin(), X.end(), S[i].first.A.x - eps) - X.begin(); R[i] = lower_bound(X.begin(), X.end(), S[i].first.B.x - eps) - X.begin(); } vector<vector<double>> A(M, vector<double>(N)); Y.resize(N); for (int i = 0; i < M; i++){ for (int j = L[i]; j <= R[i]; j++){ A[i][j] = line_intersection(S[i].first, line(point(X[j], 0), point(X[j], 1))).y; Y[j].push_back(A[i][j]); } } for (int i = 0; i < N; i++){ Y[i] = unique_eps(Y[i]); } vector<vector<int>> B(M, vector<int>(N)); for (int i = 0; i < M; i++){ for (int j = L[i]; j <= R[i]; j++){ B[i][j] = lower_bound(Y[j].begin(), Y[j].end(), A[i][j] - eps) - Y[j].begin(); } } E.resize(N - 1); for (int i = 0; i < N - 1; i++){ E[i].push_back(make_tuple(-1, -1, e)); } for (int i = 0; i < M; i++){ for (int j = L[i]; j < R[i]; j++){ E[j].push_back(make_tuple(B[i][j], B[i][j + 1], S[i].second)); } } for (int i = 0; i < N - 1; i++){ E[i].push_back(make_tuple(Y[i].size(), Y[i + 1].size(), e)); } for (int i = 0; i < N - 1; i++){ sort(E[i].begin(), E[i].end()); if (!E[i].empty()){ vector<tuple<int, int, T>> E2; int cnt = E[i].size(); E2.push_back(E[i][0]); for (int j = 1; j < cnt; j++){ if (get<0>(E[i][j - 1]) == get<0>(E[i][j]) && get<1>(E[i][j - 1]) == get<1>(E[i][j])){ get<2>(E2.back()) = f(get<2>(E2.back()), get<2>(E[i][j])); } else { E2.push_back(E[i][j]); } } E[i] = E2; } } P.resize(N + 2); P[0] = 0; P[1] = 1; for (int i = 0; i < N - 1; i++){ P[i + 2] = P[i + 1] + E[i].size() - 1; } P[N + 1] = P[N] + 1; vector<vector<int>> idL(N), idR(N); for (int i = 0; i < N; i++){ idL[i] = vector<int>(Y[i].size() + 1); idR[i] = vector<int>(Y[i].size() + 1); } idL[0] = vector<int>(Y[0].size() + 1, 0); for (int i = 0; i < N - 1; i++){ int cnt = E[i].size(); for (int j = 0; j < cnt - 1; j++){ for (int k = get<0>(E[i][j]) + 1; k <= get<0>(E[i][j + 1]); k++){ idR[i][k] = P[i + 1] + j; } for (int k = get<1>(E[i][j]) + 1; k <= get<1>(E[i][j + 1]); k++){ idL[i + 1][k] = P[i + 1] + j; } } } idR[N - 1] = vector<int>(Y[N - 1].size() + 1, P[N]); int V = P[N + 1]; vector<vector<pair<T, int>>> G(V); for (int i = 0; i <= N; i++){ for (int j = P[i]; j < P[i + 1] - 1; j++){ G[j].push_back(make_pair(get<2>(E[i - 1][j - P[i] + 1]), j + 1)); G[j + 1].push_back(make_pair(get<2>(E[i - 1][j - P[i] + 1]), j)); } } for (int i = 0; i < N; i++){ for (int j = 0; j <= Y[i].size(); j++){ G[idL[i][j]].push_back(make_pair(e, idR[i][j])); G[idR[i][j]].push_back(make_pair(e, idL[i][j])); } } return G; } int find(point p){ int x = lower_bound(X.begin(), X.end(), p.x - eps) - X.begin(); if (x == 0){ return 0; } else if (x == N){ return P[N]; } else { int ans = P[x]; for (int i = 1; i < E[x - 1].size() - 1; i++){ point A(X[x - 1], Y[x - 1][get<0>(E[x - 1][i])]); point B(X[x], Y[x][get<1>(E[x - 1][i])]); if (cross(B - A, p - A) > 0){ ans++; } } return ans; } } vector<double> area(){ int V = P[N + 1]; vector<double> ans(V, -1); for (int i = 0; i < N - 1; i++){ for(int j = 1; j < E[i].size() - 2; j++){ double l = Y[i][get<0>(E[i][j + 1])] - Y[i][get<0>(E[i][j])]; double r = Y[i + 1][get<1>(E[i][j + 1])] - Y[i + 1][get<1>(E[i][j])]; ans[P[i + 1] + j] = (l + r) * (X[i + 1] - X[i]) / 2; } } return ans; } vector<double> length(){ int V = P[N + 1]; vector<double> ans(V, -1); for (int i = 0; i < N - 1; i++){ for(int j = 1; j < E[i].size() - 1; j++){ double l = Y[i][get<0>(E[i][j])]; double r = Y[i + 1][get<1>(E[i][j])]; ans[P[i + 1] + j - 1] = hypot(X[i + 1] - X[i], r - l); } } return ans; } }; int main(){ cout << fixed << setprecision(20); while (true){ int N, R; cin >> N >> R; if (N == 0 && R == 0){ break; } vector<point> P(N + 1); for (int i = 0; i < N; i++){ cin >> P[i].x >> P[i].y; } P[N] = P[0]; vector<vector<point>> P2(N, vector<point>(6)); for (int i = 0; i < N; i++){ point A = P[i], B = P[i + 1]; if (A.x + A.y > B.x + B.y){ swap(A, B); } if (A.x - A.y > B.x - B.y){ P2[i][0] = point(A.x - R, A.y); P2[i][1] = point(B.x - R, B.y); P2[i][2] = point(B.x, B.y + R); P2[i][3] = point(B.x + R, B.y); P2[i][4] = point(A.x + R, A.y); P2[i][5] = point(A.x, A.y - R); } else { P2[i][0] = point(A.x - R, A.y); P2[i][1] = point(A.x, A.y + R); P2[i][2] = point(B.x, B.y + R); P2[i][3] = point(B.x + R, B.y); P2[i][4] = point(B.x, B.y - R); P2[i][5] = point(A.x, A.y - R); } } for (int i = 0; i <= N; i++){ P[i] = rotate(P[i], 1); } for (int i = 0; i < N; i++){ for (int j = 0; j < 6; j++){ P2[i][j] = rotate(P2[i][j], 1); } } slab_decomposition<__int128_t> D(bit_xor<__int128_t>(), 0); for (int i = 0; i < N; i++){ D.add_segment(line(P[i], P[i + 1]), 1); } for (int i = 0; i < N; i++){ for (int j = 0; j < 6; j++){ D.add_segment(line(P2[i][j], P2[i][(j + 1) % 6]), (__int128_t) 1 << (i + 1)); } } vector<vector<pair<__int128_t, int>>> E = D.build(); int V = E.size(); vector<__int128_t> A(V, -1); A[0] = 0; queue<int> Q; Q.push(0); while (!Q.empty()){ int v = Q.front(); Q.pop(); for (pair<__int128_t, int> e : E[v]){ int w = e.second; if (A[w] == -1){ A[w] = A[v] ^ e.first; Q.push(w); } } } vector<double> L = D.length(); double ans = 0; for (int i = 0; i < V - 1; i++){ if (A[i] != 1 && A[i + 1] == 1 || A[i] == 1 && A[i + 1] != 1){ ans += L[i]; } } cout << ans << endl; } }
AOJ 2394: Longest Lane
この記事は帰ってきた AOJ-ICPC Advent Calendar 2022 8 日目の記事です。
https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2394
問題概要
頂点の凸と限らない多角形が与えられる。この多角形の内部または周上に完全に含まれる線分の長さの最大値を求めよ。
解法
多角形の頂点を反時計回りに とします。
実は、考える線分の候補は多角形の 頂点を通る線分のみとしてよいです。その
頂点を
とすると、以下を調べる必要があります。
- 線分
が多角形の内部に含まれるか
から、
と逆方向にどこまで伸ばせるか
から、
と逆方向にどこまで伸ばせるか
多角形のそれぞれの辺と頂点について、線分を伸ばすことをどのように妨げるか調べます。辺については、その辺が線分 と端点以外で交わるかどうか調べ、交わる場合は交点と
の位置関係を調べればよいです。
頂点については、前後の辺も含めて位置関係を調べなくてはならないため、複雑になります。結局、以下の判定に帰着します。
点
が与えられる。半直線
を点
を中心として
まで反時計回りに回転させたとき、半直線が通過した領域に点
が与えられるか判定する。
これは と
の外積の符号で場合分けすることで簡単な判定に帰着します。以下の実装では、関数
point_in_angle でこの判定を行っています。
実装
#include <bits/stdc++.h> using namespace std; const double eps = 0.000001; const double INF = 100000000; int sign(double x){ if (x > eps){ return 1; } else if (x < -eps){ return -1; } else { return 0; } }; struct point{ double x, y; point(){ } point(double x, double y): x(x), y(y){ } point operator +(point P){ return point(x + P.x, y + P.y); } point operator -(point P){ return point(x - P.x, y - P.y); } }; double abs(point P){ return sqrt(P.x * P.x + P.y * P.y); } double dist(point P, point Q){ return abs(Q - P); } double dot(point P, point Q){ return P.x * Q.x + P.y * Q.y; } double cross(point P, point Q){ return P.x * Q.y - P.y * Q.x; } bool point_in_angle(point A, point B, point C, point P){ B = B - A; C = C - A; P = P - A; if (abs(cross(B, P)) < eps && dot(B, P) > 0){ return true; } if (abs(cross(C, P)) < eps && dot(C, P) > 0){ return true; } if (abs(cross(B, C)) < eps){ if (dot(B, C) > 0){ return false; } else { return cross(B, P) > 0; } } if (cross(B, C) > 0){ return cross(B, P) > 0 && cross(P, C) > 0; } else { return cross(B, P) > 0 || cross(P, C) > 0; } } struct line{ point A, B; line(point A, point B): A(A), B(B){ } }; point vec(line L){ return L.B - L.A; } bool segment_line_intersect(line L1, line L2){ return sign(cross(L2.A - L1.A, vec(L1))) * sign(cross(L2.B - L1.A, vec(L1))) < 0; } bool segment_intersect(line L1, line L2){ return segment_line_intersect(L1, L2) && segment_line_intersect(L2, L1); } int main(){ cout << fixed << setprecision(10); int T = 0; while (true){ int N; cin >> N; if (N == 0){ break; } vector<point> P(N * 2); for (int i = 0; i < N; i++){ cin >> P[i].x >> P[i].y; } for (int i = 0; i < N; i++){ P[N + i] = P[i]; } double ans = 0; for (int i = 1; i <= N; i++){ for (int j = i + 1; j < i + N - 1; j++){ double mn = -INF, mx = INF; bool ok = true; if (!point_in_angle(P[i], P[i + 1], P[i - 1], P[j])){ ok = false; } if (!point_in_angle(P[i], P[i + 1], P[i - 1], P[i] + (P[i] - P[j]))){ mn = 0; } if (!point_in_angle(P[j], P[j + 1], P[j - 1], P[i])){ ok = false; } if (!point_in_angle(P[j], P[j + 1], P[j - 1], P[j] + (P[j] - P[i]))){ mx = 1; } for (int k = 0; k < N; k++){ if (k != i - 1 && k != i % N && k != (j - 1) % N && k != j % N){ if (segment_line_intersect(line(P[i], P[j]), line(P[k], P[k + 1]))){ double r = cross(P[k] - P[i], P[k + 1] - P[k]) / cross(P[j] - P[i], P[k + 1] - P[k]); if (r < eps){ mn = max(mn, r); } else if (r > 1 - eps){ mx = min(mx, r); } else { ok = false; } } } } for (int k = 0; k < N; k++){ if (k != i % N && k != j % N){ if (abs(cross(P[k] - P[i], P[j] - P[i])) < eps){ if (!point_in_angle(P[k], P[k + 1], P[k + N - 1], P[k] + P[i] - P[j]) || !point_in_angle(P[k], P[k + 1], P[k + N - 1], P[k] + P[j] - P[i])){ if (dot(P[k] - P[i], P[j] - P[i]) < 0){ mn = max(mn, -abs(P[k] - P[i]) / abs(P[j] - P[i])); } else if (dot(P[k] - P[j], P[j] - P[i]) > 0){ mx = min(mx, abs(P[k] - P[i]) / abs(P[j] - P[i])); } else { ok = false; } } } } } if (ok){ ans = max(ans, (mx - mn) * dist(P[i], P[j])); } } } cout << "Case " << T + 1 << ": " << ans << endl; T++; } }