CF1446C Xor Tree
題目來源:Codeforces, Codeforces Round #683 (Div. 1, by Meet IT), CF1446C Xor Tree
題目大意
對於一個長度為 \(k\) 的序列 \(b_1,b_2,\dots,b_k\),數字互不相等,我們可以這樣構造一張圖:
圖上有 \(k\) 個節點,第 \(i\) 個點上寫着數字 \(b_i\)。對每個 \(i\),找到一個 \(j\),滿足 \(j\neq i\),且 \(b_i\operatorname{xor}b_j\) 最小。然后在 \(i,j\) 之間連一條無向邊。
我們稱一個序列 \(b\) 是好的,當且僅當由它生成的無向圖,是一個環長為 \(2\) 的基環樹。
給出一個長度為 \(n\) 的序列 \(a_1,a_2,\dots,a_n\),數字互不相等。求最少刪去多少個數,能使序列變成好的。
數據范圍:\(1\leq n\leq 2\times10^5\),\(1\leq a_i\leq 10^9\)。
本題題解
首先,任意一個序列,連出來的圖一定是一個基環樹森林(點數 \(=\) 邊數)。
考慮所有 \(\frac{n(n-1)}{2}\) 對數的 \(\operatorname{xor}\) 值,選出其中一對 \(\operatorname{xor}\) 值最小的數 \((a_x,a_y)\),則 \(a_x\) 與 \(a\) 中其他數異或,得到的結果一定大於 \(a_x\operatorname{xor}a_y\)(因為選出的是最小的,所以不可能小於;因為所有數互不相同,所以不可能等於);對 \(a_y\) 也是同理。所以 \(x\), \(y\) 一定組成了一個大小為 \(2\) 的環。
於是在只有一棵基環樹時,環的大小一定是 \(2\)。進而可以歸納證明,整個基環樹森林,沒有大小超過 \(2\) 的環。
於是問題轉化為,判斷圖的連通性:如果圖聯通,序列就是好的;否則就是不好的。
我們從高到低考慮每一位。假設當前剩余的數的總數至少為 \(3\)。把當前剩余的數分成:當前位為 \(0\) 的數、當前位為 \(1\) 的數兩類。分別放入集合 \(S_0\), \(S_1\)。
- 如果某個集合大小 \(\leq 1\),則該集合里的數,會直接掛到另一個集合的某個節點上。所以不用考慮它,直接帶着另一個集合,考慮下一位。
- 否則,對於大小至少為 \(2\) 的集合,里面的數一定會內部連邊,不會考慮另一個集合里的點(因為這樣能保證當前位的異或值為 \(0\))。於是圖就至少分裂為兩個連通塊。我們需要選擇一個集合,將其刪到只剩一個點,然后遞歸到下一位,解決另一個集合。
簡單講就是一個遞歸的過程。我們定義一個遞歸函數,\(f(S,\text{bit})\),表示對於集合 \(S\),考慮里面的數在高於 \(\text{bit}\) 的位全部相等。現在要使得 \(S\) 構成一個連通圖,最少還需刪掉多少數。那么:
注意:直接選 \(S_0,S_1\) 里大小較小的一個刪,是不對的。因為這本質是一個 DP 問題而不是貪心問題,局部的最優不一定帶來全局的最優。數據 #4 就是一個例子。
值得一提的是,如果你把找異或最小值的過程,想象成在 Trie 樹上查找,則上述的遞歸,也可以理解為在 Trie 樹上做樹形 DP。我們要保證,不存在某個節點,兩個兒子子樹大小都 \(\geq 2\)。
因為最多遞歸 \(O(\log a)\) 層,每層里集合大小 \(S\) 之和是 \(O(n)\) 的,所以總時間復雜度 \(O(n\log a)\)。
參考代碼
// problem: CF1446C
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
template<typename T> inline void ckmax(T& x, T y) { x = (y > x ? y : x); }
template<typename T> inline void ckmin(T& x, T y) { x = (y < x ? y : x); }
const int MAXN = 2e5;
int n;
uint a[MAXN + 5];
int solve(const vector<uint>& vec, int bit) {
if (SZ(vec) <= 3) {
return 0;
}
assert(bit >= 0);
vector<uint> v0, v1;
for (int i = 0; i < SZ(vec); ++i) {
if ((vec[i] >> bit) & 1u) {
v1.pb(vec[i]);
} else {
v0.pb(vec[i]);
}
}
return min(solve(v0, bit - 1) + max(0, SZ(v1) - 1), solve(v1, bit - 1) + max(0, SZ(v0) - 1));
}
int main() {
cin >> n;
vector<uint> vec;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
vec.pb(a[i]);
}
cout << solve(vec, 29) << endl;
return 0;
}
