$\newcommand{\O}{\mathrm{O}}$
最大独立集合(最大安定集合)問題を解くアルゴリズム. 最大独立集合問題とは, 重みなし無向グラフ $G = (V, E)$ が与えられたときに 頂点集合 $U (\subseteq V)$ に含まれる任意の異なる $2$ 頂点 $u, v$ について $(u, v) \not \in E$ を満たすような集合で頂点数最大のものを求める問題である.
この問題は(判定問題としてみたとき) NP 完全であることが知られており, 多項式時間で解くアルゴリズムは存在しないだろうと考えられている.
以下の実装は枝刈りでの実装で 岩田さんのスライド を参考に実装をおこなった. (端的に説明されていて分かりやすい.)
計算量は $\O (n 1.467^n)$ であるが, スライドにあるとおりこのような探索問題の実際の計算時間は理論評価した計算量と比べて非常に高速に動作することが多く, verify 問題にあるように $120$ 頂点のグラフについてもおよそ $2$ sec 程度で最適解を求めることができる.
実装において高速化のためグラフの頂点や枝を削除していく部分を順序付き集合ではなくリストでおこなっている. (この実装は stl の std::list を用いてできないので 自作のリスト を用いて実装した.)
ちなみに (最大独立集合のサイズ) = $|V| -$ (最小頂点被覆のサイズ) = ($G$ の補グラフのクリーク数) という関係が成り立つので(証明はそこまで難しくない), これらの問題に対してもこのアルゴリズムを用いることができる.
yukicoder の verify 問題で高速に通している人たちはおそらく 富田らの論文 の最大クリーク問題を高速に解くアルゴリズムを用いているものと思われる.
(注) 多重辺が存在しないことおよび頂点数が小さいこと(高々 $127$ 以下であること)を仮定しています.
手元で作ったランダムケースについては以下のアルゴリズムよりもこちらのアルゴリズムを用いて最小頂点被覆問題として解くほうが少し速く動作した.
(関数)
add_edge$(u, v)$ : 無向辺$(u, v)$ を追加する
solve(): 最適値を返す($ans \_ set$ に最適値を実現する頂点集合が格納される)
時間計算量: $\O (n1.466^n)$
template<typename T> class ListIterator; template<typename T> class List { public: using iterator = ListIterator<T>; private: struct block { block *prev, *next; T data; block() : prev(this), next(this){} block(T _data) : prev(this), next(this), data(_data){} block(block *_prev, block *_next) : prev(_prev), next(_next){} }; int N; size_t sz; vector<block> container; friend ListIterator<T>; // cur を nx の前に挿入 iterator insert(block* const nx, block* const cur){ ++sz; cur->prev = nx->prev, cur->next = nx; nx->prev->next = cur, nx->prev = cur; return iterator(cur); } // cur を削除 iterator erase(block* const cur){ --sz; cur->prev->next = cur->next, cur->next->prev = cur->prev; return iterator(cur->next); } public: iterator insert(const iterator nx, const iterator cur){ return insert(nx.ls_ptr, cur.ls_ptr); } iterator insert(const iterator nx, int cur_index){ return insert(nx.ls_ptr, &container[cur_index]); } iterator insert(int next_index, int cur_index){ return iterator(&container[next_index], &container[cur_index]); } iterator erase(const iterator cur){ return erase(cur.ls_ptr); } iterator erase(int cur_index){ return erase(&container[cur_index]); } public: List() : N(0), sz(0){} List(int _N) : N(_N), sz(N), container(_N+1){} List(const vector<T>& _data) : List((int)_data.size()){ build(_data); } List(const List& ls) : N(ls.N), sz(ls.sz), container(ls.container){} void build(const vector<T>& _data){ for(int i = 0; i <= N; i++){ container[i].prev = &container[(i+N)%(N+1)]; container[i].next = &container[(i+1)%(N+1)]; if(i < N) container[i].data = _data[i]; } } void push_back(const T _data){ ++N, ++sz, container.push_back(_data); } void build(){ container.push_back(T()); for(int i = 0; i <= N; i++){ container[i].prev = &container[(i+N)%(N+1)]; container[i].next = &container[(i+1)%(N+1)]; } } friend ostream& operator<< (ostream& os, List& ls){ for(auto& val : ls) os << val << ' '; return os; } const T& operator[](size_t index) const { return container[index].data; } T& operator[](size_t index){ return container[index].data; } size_t size() const { return sz; } bool empty() const { return (size() == 0); } T& front(){ return container[N].next->data; } T& back(){ return container[N].prev->data; } iterator begin(){ return iterator(container[N].next); } iterator end(){ return iterator(&container[N]); } }; template<typename T> class ListIterator { private: friend List<T>; typename List<T>::block *ls_ptr; using iterator_category = std::bidirectional_iterator_tag; using value_type = T; using difference_type = T; using pointer = T*; using reference = T&; private: ListIterator(typename List<T>::block *ls) : ls_ptr(ls){} public: ListIterator(){} ListIterator(const ListIterator& itr) : ls_ptr(itr.ls_ptr){} ListIterator& operator=(const ListIterator& itr) & { return ls_ptr = itr.ls_ptr, *this; } ListIterator& operator=(ListIterator&& itr) & { return ls_ptr = itr.ls_ptr, *this; } reference operator*() const { return ls_ptr->data; } pointer operator->() const { return &(ls_ptr->data); } ListIterator& operator++(){ return ls_ptr = ls_ptr->next, *this; } ListIterator operator++(int){ ListIterator res = *this; return res.ls_ptr = ls_ptr->next, res; } ListIterator& operator--(){ return ls_ptr = ls_ptr->prev, *this; } ListIterator operator--(int){ ListIterator res = *this; return res.ls_ptr = ls_ptr->prev, res; }; bool operator==(const ListIterator& itr) const { return !(*this != itr); }; bool operator!=(const ListIterator& itr) const { return ls_ptr != itr.ls_ptr; } }; // 多重辺はなし class MIS { private: struct edge { int to, rev; }; struct info { int from, to; List<edge>::iterator next; info(int ag1, int ag2, List<edge>::iterator ag3) : from(ag1), to(ag2), next(ag3){} }; using LL = unsigned __int128; int V; vector<List<edge> > G; List<int> rem_ver; vector<int> cand, deg, is_rem; vector<vector<info> > edge_info; LL not_use_ver; int get_first_element(LL num){ int x = __builtin_ffsll(num & 0xffffffffffffffff); int y = __builtin_ffsll((num >> 64) & 0xffffffffffffffff); return x ? (x - 1) : (y + 63); } bool finish(){ return (int)cand.size() + (int)rem_ver.size() <= ans_size; } bool erase(int u, bool use, bool alr_use, int& add_size, vector<pair<int, List<int>::iterator> >& erase_ver){ erase_ver.emplace_back(u, rem_ver.erase(u)), is_rem[u] = 0; if(use && !alr_use){ add_size++, cand.push_back(u); }else{ if(finish()) return false; } for(auto& e : G[u]){ edge_info[u].emplace_back(e.to, e.rev, G[e.to].erase(e.rev)), deg[e.to]--; if(deg[e.to] == 0) erase_ver.emplace_back(e.to, rem_ver.erase(e.to)), is_rem[e.to] = 0; if(use){ if(deg[e.to] > 0) not_use_ver |= ((LL)1 << e.to); }else if((not_use_ver >> e.to & 1) == 0 && deg[e.to] == 0){ add_size++, cand.push_back(e.to); } } return !finish(); } bool erase_rec_set(bool flag, int& add_size, vector<pair<int, List<int>::iterator> >& erase_ver){ while(not_use_ver != 0){ int v = get_first_element(not_use_ver); not_use_ver ^= ((LL)1 << v); if(deg[v] == 0) continue; if(!erase(v, flag, flag, add_size, erase_ver)) return false; } return true; } bool erase_rec(int u, bool use, int& add_size, vector<pair<int, List<int>::iterator> >& erase_ver){ if(!erase(u, use, false, add_size, erase_ver)){ not_use_ver = 0; return false; } while(not_use_ver != 0){ if(!erase_rec_set(false, add_size, erase_ver)){ not_use_ver = 0; return false; } } return true; } bool preprocessing(int& add_size, vector<pair<int, List<int>::iterator> >& erase_ver){ for(int u : rem_ver){ if(is_rem[u] == 1 && deg[u] <= 1){ if(!erase_rec(u, true, add_size, erase_ver)) return false; } } return true; } void postprocessing(int& add_size, vector<pair<int, List<int>::iterator> >& erase_ver){ for(int i = 0; i < add_size; i++){ cand.pop_back(); } for(int i = (int)erase_ver.size() - 1; i >= 0; i--){ auto& p = erase_ver[i]; while(!edge_info[p.first].empty()){ auto& dat = edge_info[p.first].back(); G[dat.from].insert(dat.next, dat.to), deg[dat.from]++; edge_info[p.first].pop_back(); } rem_ver.insert(p.second, p.first), is_rem[p.first] = 1; } } bool check(){ if(rem_ver.empty()){ if((int)cand.size() > ans_size){ ans_size = (int)cand.size(), ans_set = cand; } return false; } return true; } void transition(int next_ver, bool use){ int pl_add_size = 0; vector<pair<int, List<int>::iterator> > pl_erase_ver; if(!erase_rec(next_ver, use, pl_add_size, pl_erase_ver)){ return postprocessing(pl_add_size, pl_erase_ver); } if(check()) judge(); return postprocessing(pl_add_size, pl_erase_ver); } int find_max_degree_ver(){ int max_deg = -1, next_ver = -1; for(int v : rem_ver){ if(deg[v] > max_deg) max_deg = deg[v], next_ver = v; } return next_ver; } void judge(){ int add_size = 0; vector<pair<int, List<int>::iterator> > erase_ver; if(!preprocessing(add_size, erase_ver)) return postprocessing(add_size, erase_ver); if(!check()) return postprocessing(add_size, erase_ver); int next_ver = find_max_degree_ver(); transition(next_ver, true); if(deg[next_ver] > 1 && (int)cand.size() + (int)rem_ver.size() > ans_size + 1){ transition(next_ver, false); } return postprocessing(add_size, erase_ver); } public: vector<int> ans_set; int ans_size; MIS(int node_size) : V(node_size), G(V), rem_ver(V), deg(V, 0), is_rem(V, 1), edge_info(V), not_use_ver(0), ans_size(0){ vector<int> _init(V); iota(_init.begin(), _init.end(), 0); rem_ver.build(_init); } void add_edge(int u, int v){ G[u].push_back((edge){v, (int)G[v].size()}); G[v].push_back((edge){u, (int)G[u].size() - 1}); deg[u]++, deg[v]++; } int solve(){ for(int i = 0; i < V; ++i) G[i].build(); judge(); return ans_size; } };
yukicoder : シャイな人たち (2)
提出コード
Atcoder : Mixture Drug
提出コード
yosupo さんの libary checker : Maximum Independent Set
提出コード