第7回では, の部分集合系 が与えられたときに, が開集合系の定義を満たす( が位相空間になる)かどうかの判定について書きます.
計算量は の要素数を , ワードサイズを として, 時間計算量 , 空間計算量 です. これが多項式時間になるという雰囲気が好きです.
あらすじ: を含む最小の位相空間の要素数が であるかを判定すればよい. を含む最小の位相空間と対応する preorder を作り, 縮約し DAG にする. その DAG についてある種の DFS を行い, 開集合の個数を数え上げる. ただし, 個数が を超えたら切り上げる.
問題設定
正整数 と, の要素数 の部分集合系 が与えられます. 「 が開集合系かどうか判定して」と電話したら, あなたはすぐに答えてくれますか?
preorder と有限位相空間
有限位相空間と前順序 (preorder) という二項関係とには全単射があります. 有限位相空間を ( は有限集合、 は開集合系 )と書くことにします. また, preorder の二項関係を とします.
開集合系 → preorder
なら, であることは,「任意の の要素 について, なら が成り立つ」とき, かつそのときに限ることにします. そうすると, 開集合系から preorder が出来ます.
preorder → 開集合系
に対し, を満たす の集合を とします. そうして
とすると, preorder から開集合系が出来ます.
証明
J - Set Construction 解説:ストーリー
https://atcoder.jp/contests/ttpc2023/editorial/7404
に書きました
を含む最小の位相空間
与えられた の部分集合系 について, 「開集合系 → preorder」 にしたがって順序を作ると, それは preorder になります.
これが を含む最小の位相空間 と対応する preorder であることを示します.
「preorder → 開集合系」の各 () について, です. 以下の補題より がわかるので示されます.
補題1 任意の開集合 について が成り立つ.
証明: なので がいえます.
とします. をとると, ある が存在して となります. の定義より となりますが, なので矛盾です. ■
が開集合系, なら
↑これは が から生成される開集合系であることを言っています.
を に対応する preorder とします. () を となる の集合とします. そうすると, より, 任意の開集合 について が言えます.
補題2 任意の に対し である.
を仮定し, をとります. なので, ある が存在して かつ です.
いま でしたが, なので です. これは に矛盾します.■
補題3 の任意の開集合 について, である.
証明: 任意の に対して補題2より なので, です.
いま, 各 に対して なので です. よって です. したがって, であり, です.■
↑ の補題3より が言えました. これで が から生成される最小の開集合系であることが言えます.
位相空間判定
Step.1 preorder を作る
があるとき , 他は というような行列 を作ると, これは preorder を表します.
bitset 高速化を用いると です.
Step.2 preorder を SCC して poset (半順序集合)にする
preorder は強連結成分を縮約することで poset (半順序集合)になります.
ちなみに poset は有限 位相空間と一対一対応があります.
ここは SCC をすればよいので です.
Step.3 DFS をする
poset の要素数を とします. poset が推移律を満たす DAG であることに注目し, 位相空間の開集合の個数を重複なく数え上げます. antichain の個数を数える操作と同じだと思います.
次の DFS に従います.
- の入次数 の点 を見つける. を使わない場合, から を消去する. を使う場合, から集合 の要素をすべて消去する.
個数が に達した場合, DFS をやめます.
bitset 高速化を用いると です.
Step.4 判定
個数が なら は位相空間です. そうではない場合, 位相空間ではありません.
コード
の各要素を bit 目が のとき を含む / のとき を含まない, というようにして, 以上 未満の整数で表すこととします.
#include<bits/stdc++.h> #include<atcoder/scc> using namespace std; typedef long long ll; int main(){ int n, m; cin >> n >> m; vector<ll> a(m); for (int i=0; i<m; i++){ cin >> a[i]; } // make preorder vector<ll> e(n, (1LL << n) - 1); for (int i=0; i<n; i++){ for (int j=0; j<m; j++){ if (a[j] >> i & 1){ e[i] &= a[j]; } } } // scc atcoder::scc_graph scc(n); for (int i=0; i<n; i++){ for (int j=0; j<n; j++){ if (e[i] >> j & 1){ scc.add_edge(i, j); } } } vector<vector<int>> new_graph = scc.scc(); vector<int> cores(n); int k = new_graph.size(); for (int i=0; i<k; i++){ for (int j: new_graph[i]){ cores[j] = i; } } // make dag vector<ll> ne(k); for (int i=0; i<n; i++){ for (int j=0; j<n; j++){ if (cores[i] == cores[j]){ continue; } if (e[i] >> j & 1){ ne[cores[i]] |= 1LL << cores[j]; } } } for (int i=0; i<k; i++){ ne[i] |= 1LL << i; } // dfs int cnt = 0; ll mask = (1LL << k) - 1; auto dfs = [&](auto self, ll state, int now) -> void { if (state == 0){ cnt++; return; } while (!(state >> now & 1)) now++; // NOT USE self(self, state ^ (1LL << now), now + 1); if (cnt > m){ return; } // USE self(self, state & (mask ^ ne[now]), now + 1); return; }; dfs(dfs, mask, 0); if (cnt != m){ cout << "No\n"; }else{ cout << "Yes\n"; } }
系
TTPC2023 J の判定
https://atcoder.jp/contests/ttpc2023/tasks/ttpc2023_j
TTPC2023 J - Set Construction の正誤判定が で出来ます。本番のジャッジでは で結構な時間が掛かっていたのですが、かなり高速になると思います。
Southeastern European Regional Programming Contest 2023 A - AND-OR closure
だいたい から生成される位相空間の開集合の数え上げです.
残ってる点への出次数が 以下の場合に場合分けすることで, 最大独立集合の DFS と同じような評価で などができると思います.
↓の解説によると半分全列挙で らしいですが, それより高速にできることになります.
https://contest.ucup.ac/download.php?type=attachments&id=1452&r=1
参考文献
Southeastern European Regional Programming Contest 2023 Editorial
https://contest.ucup.ac/download.php?type=attachments&id=1452&r=1