noshi91のメモ

データ構造のある風景

割れない時の行列式

概要

逆元が必ずしもない状況で行列式を計算する方法を紹介します

一般の可換環上の行列式

n91lib_rs/division_free_determinant.rs at master · noshi91/n91lib_rs · GitHub

除算を使わない  \Theta(n^4) のシンプルなアルゴリズムが知られています。 除算無しでも  o(n^3) になるらしいのですが、詳しく勉強してないので詳細は分かりません。

 {\rm mod} 合成数

掃き出し法のアルゴリズムを少し変えるだけで高速に計算できます。 掃き出し法は「ある行を使って別の行の先頭要素を 0 にする」を繰り返すアルゴリズムなので、この部分を何とかできればいいです。 そこで、0 にしたい列の要素を一旦 ( {\rm mod}\ m ではなく) ただの数だと思って、ユークリッド互除法を行います。 ユークリッド互除法は定数倍と加減算なので、 {\rm mod}\  m でも問題なく計算できます。 これを繰り返せば  O(n^3 \log(m))アルゴリズムが得られます。  {\rm gcd} を繰り返しとる操作は計算量が落ちるので、実際には  O(n^3 + n^2 \log(m)) になっています。

さらに計算量を落とすことも出来ます。 そもそもユークリッド互除法の部分は 2 要素間だけで計算できるので、最終的にどういう線形変換になったかだけを追って、最後に行ベクトル同士を足し引きすればよいです。 これにより  O(n^3 + n \log(m)) となります。

ユークリッド互除法が行えるなら何でもいいので、多項式環などでも使うことが出来ます。




----------- 以下、yukicoder のネタバレを含みます ----------



















yukicoder No.1303 Inconvenient Kingdom - ひとなので

この解法で掃き出し法が機能しているのは、元のグラフが連結なので行列式が 0 でない、などの性質を利用しています。 先ほども述べた通り多項式ユークリッド互除法が使えるので、それを用いれば ad-hoc な証明をしなくても AC を取ることが出来ます。

https://yukicoder.me/submissions/586286

余談ですが、私は本番でこれに気付かなかったので、一般の可換環用の  \Theta(n^4)アルゴリズムの方を使って AC しました。

ICPC2020 国内予選 参加記

Poyashi で参加して 6 完 4 位でした

開始前

リハーサル参加のためかなり早めに来たが、模擬に参加してるのでほとんど練習の必要がないことに気付く。 ライブラリを書きながら開始を待つ。

コンテスト開始後

記憶がかなり曖昧なので間違ってるかも。

A moyashi B pulmn C noshi で読む。 AB はすぐに解けるが C が普通に解けないので D を読んだら D も解けなくて困った。

moyashi pulmn に C を伝えるが解法が出ないため、ぶん回し覚悟で重いコードを書く。 サンプルですら異常な時間が掛かっており不安になるが、とりあえず裏で回しておくことにした。(後で通った)

D が分かったので実装する。この間に pulmn が F を解いていた(問題見てなかった)。

pulmn「E は 230

noshi「100 時間くらいかかる計算」

pulmn「fibonacci になった」

noshi「215 になった」

moyashi「そうじゃん、割り算間違えてた」

3 人で E やってもしょうがないだろということで moyashi と pulmn が同時に E をコーディングして、どちらかで通ればいいですねをした。

その間に noshi が G を考えていた。

E が通ったが moyashi pulmn は G が苦手らしいので H をやってもらう。

こんなのは最小費用流の双対なんですねと言いながら双対を取ったら、解が一致しないので苦戦する。 実は weight と capacity を swap してしまっていたことが分かり、双対は正しく取れていた(は?)。 結局方針があってるのかもよく分からなかった。

終了

感想

2019 予選, 2020 模擬で大敗していたので、良い感じの結果が出て満足した。 序盤の問題が通り終わったら自分は全ての問題に目を通すべきだったというのと、後半時間が余りまくったのでもう少し考察を速めたい。

私用メモ: 畳み込めるものまとめ

長さ  n 同士の列を畳み込むのが  \mathrm{O}(n ^ {2-\epsilon}) でできるやつ

 \displaystyle c _ k = \bigoplus _ {i \circ j = k} a _ i \otimes b _ j

 \circ  \oplus  \otimes 時間計算量 備考
 +  +  \times  \mathrm{O}(n \log (n))  \mathbb{C}, 高速フーリエ変換
 +  +  \times  \mathrm{O}(n \log(n) \log(\log(n))) 環, Schönhage–Strassen
 \cap  +  \times  \mathrm{O}(n \log (n)) 環, 高速ゼータ変換, メビウス変換
 \cup  +  \times  \mathrm{O}(n \log (n)) 環, 高速ゼータ変換, メビウス変換
 \Delta  +  \times  \mathrm{O}(n \log (n)) 環, 2 で割れる, 高速アダマール変換
 \amalg  +  \times  \mathrm{O}(n (\log (n)) ^ 2) 環, subset convolution
 \mathrm{gcd}  +  \times  \mathrm{O}(n \log (\log (n))) 環, 高速ゼータ変換, メビウス変換
 \mathrm{lcm}  +  \times  \mathrm{O}(n \log (\log (n))) 環, 高速ゼータ変換, メビウス変換
 \times \pmod{n}  +  \times  \mathrm{O}(n \log (n))  n素数,  \mathbb{C}, 高速フーリエ変換
 ※  +  \times  \mathrm{O}(n (\log (n) ) ^ 2)  \mathbb{C}, ※  i + j ただし  i \lt j, 高速フーリエ変換, 分割統治
 +  \max  \min  \mathrm{O}(n \sqrt{n \log (n)})
 +  \min  +  \mathrm{O}(n)  b が下に凸
 +  \min  +  \mathrm{O}(n \alpha(n))  b が上に凸
 +  \times  \mathrm{O}(n (\log(n)) ^ 2)  \mathbb{C}, ※多変数畳み込み
 +  \times  \mathrm{O}(n (\log(n)) ^ 2 \log(\log(n))) 環, ※多変数畳み込み

 \circ が集合の演算になっているものは、添え字を集合の整数表現と解釈して演算することを意味する。


実装など

n91lib_rs/subset_convolution.rs at master · noshi91/n91lib_rs · GitHub

n91lib_rs/max_min_convolution.rs at master · noshi91/n91lib_rs · GitHub

Concave Max Plus Convolution | Library

多変数畳み込み(切り捨て)のアルゴリズム – 37zigenのHP

Range Mode Query 空間 Θ(n) 構築 Θ(n√n) クエリ Θ(√n)

概要

Range Mode Query - data-structures

これをもう少しだけ詳しめに解説します。 面白いので好きなアルゴリズムです。

アルゴリズム

まずは平方分割して、ブロックの境界を端点とする区間 ( \Theta(n) 個ある) の全てについて最頻値とその頻度を計算します。 他にも前計算するものがありますが、説明が難しいのでクエリの処理方法を追いながらやっていきます。

区間  \lbrack l, r) の最頻値を計算したいとします。 まずは前計算したものを使って、暫定的な最頻値と頻度 (  =: freq ) を取得します。 この暫定解が更新されるとすれば、それはブロックからはみ出た左右の端数の部分に含まれるいずれかの値によるものになります。 従って、端数の部分の全要素について、その要素が  \lbrack l, r) に出現する回数が  freq を超えるか判定して、適切に更新する、というアルゴリズムがまず思い浮かびます。

f:id:noshi91:20201026134939j:plain

クエリを  {\rm O}(\sqrt n) にしたいので、それぞれの要素について  {\rm O}(1) で頻度を取得したくなりますが、 {\rm O}(n) の空間計算量でこれは絶望的です。

そこで見る方向を変えて、「左側の端数部分に含まれる各要素  x について、その  freq 個後ろの  x の位置が  r より手前にあるか」という判定を考えます。 この判定自体は、全ての数についてその数だけ抜き出した index の列を持っておけば空間計算量  \Theta(n) 、時間計算量  \Theta(1) で可能です。 これを用いた「 r より手前にあったら  freq が更新されるということなので、 freq を 1 増やして繰り返す」というアルゴリズムは、 \Theta(\sqrt n) の時間計算量になります。 なぜなら、ステップを 1 つ進めるたびに「 x では更新されなくなったので、次の数に移る」「 freq が 1 増える」のいずれかが発生し、 freq は最初の暫定解から  \Theta(\sqrt n) しか増え得ないからです。 右側の端数部分に含まれる数については逆に、 freq 個手前の  x の index が  l より後ろにあるかを判定すれば全く同様です。

部分永続 Union Find Θ(log(log(n)))

概要

部分永続 Union Find のアルゴリズムの変種を考えました
注意: 実測では使い物にならない気がします

空間計算量  \mathrm{O}(q + n\log(\log(n)))

-  \mathtt{init}(n)
単一の要素からなる集合  n 個で初期化する
時間計算量  \Theta(n)

-  \mathtt{unite}(x, y)
 x y が属する集合を併合する
時間計算量  \Theta(\log(\log(n))) \ \mathrm{expected} \ \mathrm{amortized}

-  \mathtt{find}(t, x)
 t 回目に  \mathtt{unite} が呼ばれた直後に  x が属していた集合の代表元を取得する
時間計算量  \Theta(\log(\log(n)))

-  \mathtt{size}(t, x)
 t 回目に  \mathtt{unite} が呼ばれた直後に  x が属していた集合の要素数を取得する
時間計算量  \Theta(\log(\log(n))) \ \mathrm{expected}


アルゴリズム

通常の Union Find のアルゴリズムにおいて、weighted union rule は適用し、経路圧縮は行わないことで得られる木を考えます。  \mathtt{find}(t, x) は、この木において  x の祖先で時刻  t にまだ根だったものの中で最も深いものを発見すると表現できます。 もし  \mathtt{unite} が全て事前に与えられているならば、ダブリングを用いることで  \Theta(\log(\log(n))) の時間計算量で  \mathtt{find} に答えることが出来ます。 weighted union rule により木の高さが  \Theta(\log(n)) に抑えられることに注意してください。

よって、 \mathtt{size} を一旦忘れると、ダブリングのための情報(各頂点について、 2^i 個上の頂点)を  \mathtt{unite} の度に更新できれば良いです。  y x の子にするとき、新たに情報を更新する必要があるのは  y の子孫で  x からの距離が二冪の頂点です。 各頂点  v \mathtt{list}(v, i):  v の子孫で  v からの距離が  2^i の頂点のリストを管理していれば、これは効率的に列挙可能です。 例えば、 x から距離  8 の頂点は  x から距離  4 の頂点から更に距離  4 の頂点です。 ポテンシャルとして「 \min \lbrace n, q \rbrace\log(\log(\min \lbrace n,q \rbrace)) - 計算されたダブリングのテーブルの要素数」を考えれば、テーブルの更新の時間計算量はならすことができます。

 \mathtt{size} を処理するために、各頂点  v (t, s): 時刻  t v を代表元とする集合の要素数 s に更新された、という情報を管理します。  \mathtt{size}(t, x) クエリは  \mathtt{find}(t, x) において要素数 t の直前に更新されたときの対応するデータを報告すればよいです。  t q を超えないため、これは Y-fast-trie を用いて  \Theta(\log(\log(q))) \ \mathrm{expected} で処理できます。 長さ  q のテーブルを持って適宜変換することにより、 \mathtt{unite} が呼ばれた回数ではなく  \mathtt{unite} で実際に集合が併合された回数を時刻として利用できます。 これにより時刻は常に  n 未満になり、時間計算量は  \Theta(\log(\log(n))) \ \mathrm{expected} になります。

その他

複雑すぎて間違えてそう、実装する気にもならない

多分もっとまともなアルゴリズムがある

代数的構造を乗せるデータ構造の設計について

概要

SegmentTree などのように、代数的構造 (特にモノイド) を乗せるデータ構造は多い。 この部分の設計は様々だが、「静的メンバを実装した型を受け取る」設計を他の設計と比較する。


設計のバリエーション

A: std::function で関数オブジェクトを持つ

template <class T> class segment_tree {
  std::function<T(T, T)> op;
  T id;
};

B: 関数オブジェクトをそのまま持つ

template <class T, class F> class segment_tree {
  F op;
  T id;
};

C: 演算子オーバーロードされた型を受け取る

struct add {
  int val;
  add operator+(add r) { return add(val + r.val); }
  add() : val(0) {}
  add(int x) : val(x) {}
};

template <class T> struct segment_tree {};

D: メンバを実装したオブジェクトを持つ

struct add {
  using T = int;
  int op(int l, int r) { return l + r; }
  int id;
};

template <class M> struct segment_tree {
  using T = typename M::T;
  M mo;
};

E: 静的メンバを実装した型を受け取る

struct add {
  using T = int;
  static int op(int l, int r) { return l + r; }
  static inline int id;
};

template <class M> struct segment_tree { using T = typename M::T; };

D, E については、id をメンバ変数ではなくメンバ関数として持つ実装もある。 その他、継承を利用する実装もあるようだが詳しくないので割愛。

著者は E を常用している。 実装例


E の利点

  • A と比較して、std::function を使用していない。
    • 高速になる。
  • A, B, D と比較して、データ構造内部に一切オブジェクトを持たなくてよい。
    • 永続データ構造など、大量のオブジェクトを作成するときに空間が削減できる。
    • meldable heap や平衡二分木など融合を行うデータ構造で、どちらのオブジェクトを使用すればいいのかなどの問題が起こらずスッキリしている。
    • データ構造内部でさらに構造体を定義したとき、そのメンバ関数で演算が使える。
  • C と比較して、扱いたい型をそのまま値に出来る。
    • 値をラップしたりはがしたりする手間が無い。
  • C と比較して、演算子のネタ切れなどが起こらない。
    • 環など 2 つの単位元を持つ構造は、デフォルトコンストラクタだけだと表現できない。


E の欠点

  • A, B と比較して、構造体を書く必要がある。
    • operator+ と 0 など、典型的な演算はライブラリにしておくことで回避できる。
    • ライブラリ化していない変な演算は手間が増えてしまう。
  • A, B, D と比較して、内部にオブジェクトを持てない。
    • ローカル変数を参照するような演算の表現には静的メンバを使用する必要があり、面倒。
  • C と比較して、関数を明示的に呼び出す必要がある。
    • C は + などを書くだけなので、読みやすく短い。


「あーだこーだー」 第四回 非公式まとめ

注意: この記事は非公式です。また、情報の正確性や取捨選択について著者は一切の保証をしません。

概要

コンテスト関係で重要と思われる情報を抜粋、要約しました。 雑談などの情報は省略、あるいは大筋のみになっています。


まとめ

AtCoderの公式生放送「あーだこーだー」 第四回


ジャッジアップデート関連

8:31~

  • 過去問の言語アップデートについて 9:44~
    • 過去の環境で動くか調査をする必要がある
    • TOPSIC に提供しているジャッジをアップデートする必要がある兼ね合い
    • 一番遅くて 5 月上旬、早くて今週中 11:57~
  • 作業が間に合えば PAST は新ジャッジで行う 12:17~
  • 過去の提出は残るが、言語一覧が変わるため検索で探すのが大変になるかもしれない 13:48~
  • 各言語のアップデート内容紹介 13:21~


TOPSIC 紹介

10:23~


PAST 関連

32:51~

  • PAST は年 4 回のつもり 8:25~
  • 第二回の問題もサンプルとして見れるようになる予定だが、すぐではない 37:43~
    • 3 日間くらいは問題が伏せられている予定
  • 銀行決済が増えた 40:47~
  • 第三回の日程がおそらく 6 月くらいになる 42:13~
  • 団体受験などの資料が必要な場合 @chokudai に DM で請求 42:29~


World Tour Finals 関連

52:16~

  • コロナ (ウィルス) が落ちつく気配が今のところないのでまだまだ延期 52:43~
  • 出来る限りやりたいと思っている
  • 今年も多分やる