こどふぉのこの回、いい問題が多かった気がするので解き直し。最上位の桁から順に見ていって、0 と 1 とに分類していって...とするのはよく見る気がする!!!
問題概要
どの要素も互いに相異なる非負整数列 が good であるとは、以下の条件を満たすことと定義する。
- 数列の各要素を頂点に対応させたグラフを考える
- 各 に対して、 XOR が最小となるような を としたとき、頂点 と頂点 を辺で結ぶ (向きなし, 多重辺は認める)
- このように定義したグラフが連結である
今、 要素の互いに相異なる非負整数列 が与えられる。この数列からいくつかの要素を除去することで、good となるようにしたい。除去すべき要素数の最小値を求めよ。
制約
考えたこと
状況がよくわからないので色々手を動かして考えてみる。たとえば
5 1100000 1100001 1100100 1100101 1100110
といったケースを考えてみる。このとき、前半 2 つと、後半 3 つとでそれぞれ連結成分を形成している。そのようになる理由を考察してみた。
まず、4 つの整数 に対して、ある桁 が存在して
- 桁目より上の桁の値はすべて等しい
- 桁目については、 は 0 で、 は 1 である
という状況にあったとする。このとき、 グループと、 グループとが、グループの垣根を超えて辺で結ばれることはない。なぜなら、同一グループ間で辺を結んでおけば、 桁目も含めてそれより上の桁はすべて 0 にできるからだ。
ゆえに、すべて連結にするためには、 桁目が「0 のグループ」と「1 のグループ」のうちの少なくとも一方がサイズ 1 以下になる必要がある。
上位桁から再帰的に木を作る
これはこどふぉだと、本当によくみる視点だと思う。上位桁から順に見ていって、
桁目について、
- 「0 グループ」を連結にして、「1 グループ」のサイズを 1 にする
- 「1 グループ」を連結にして、「0 グループ」のサイズを 0 にする
という 2 つの場合のうち、答えが小さい方を返すようにすればよい。そして各グループを連結にする問題は、再帰的に解くことができる。
再帰深度は高々 31 (最大桁数) 。計算量は となって十分間に合う。
コード
#include <bits/stdc++.h> using namespace std; int N; vector<long long> A; int DIGIT = 32; long long solve(int left, int right) { if (right - left <= 1) return 0; int d = DIGIT; for (; d >= 0; --d) { bool f1 = (A[left] & (1LL<<d)); bool f2 = (A[right-1] & (1LL<<d)); if (f1 != f2) break; } int mid = left; for (; mid < right; ++mid) if (A[mid] & (1LL<<d)) break; return min(solve(left, mid) + (right - mid - 1), solve(mid, right) + (mid - left - 1)); } int main() { cin >> N; A.resize(N); for (int i = 0; i < N; ++i) cin >> A[i]; sort(A.begin(), A.end()); cout << solve(0, N) << endl; }