Codeforces Round #683 (Div. 2, by Meet IT) E
Codeforces Round #683 (Div. 2, by Meet IT) E
大意
給你 \(n\) 個各不相同的數,寫在 \(n\) 個點上。
記寫在第 \(i\) 個點上的數為 \(a_i\) ,則對於任意點 \(i\) 會與使 \(a_i\oplus a_j\) 最小化的點 \(j\) 連一條無向邊。
如果兩個點互相連邊只計算一條。
問你最少去掉幾個點之後能讓剩下的圖為一顆樹。
思路
好巧妙的題...
-
原圖一定是樹或森林,不可能出現環。
證:
不失一般性,如果存在環,我們將環取出,重新標號為 \(1,...,k\) ,規定 \(a\) 連向 \(a+1\) , \(k\)
考慮 \(1\rightarrow2\) 的邊,按照規定,因為有 \(2\rightarrow 3\) ,所以 $ a_2\oplus a_3 < a_1\oplus a_2$ 。
考慮 \(3\rightarrow 4 \ ...\) 顯然最後有 \(a_k\oplus a_1 < a_{k-1}\oplus a_k < ... <a_1\oplus a_2\) 又 \(a_1\oplus a_2 < a_1\oplus a_k\) ,所以不難發現這個環是非法的。
也就是說按照題述規則連結不會出現環。
-
將 \(a_i\) 按照二進位制下最大的 \(1\)
即 \(\exists k\ ; s_i\in S_1,s_j\in S_0\ ;s_j<2^k\leq s_i<2^{k+1}\)
可以發現此時若 \(|S_0|>1\ and\ |S_1|>1\) 那麼原圖一定不是一棵樹。
因為兩個集合的點只會與和自己處於相同集合內的點連邊。
因為最少刪除就是最多保留。
所以我們最多隻能讓其中一個集合保留一個點,另外一個保留儘量多的點。
記 \(R(S_i)\) 為該集合最多保留的點, \(S_j,S_k\) 為 \(S_i\) 在上述分法下的子集。
按照上述分法,兩個子集的最多可以保留的點的數量是互不影響的,因為它們之間在元素數量大於一時不可能有邊。
那麼當 \(R(S_j) \neq 0 \ and \ R(S_k) \neq 0\) ,能發現 \(R(S_i) = max(R(S_j),R(s_k))+1\) 。
如果 \(R(S_j) = 0\) 那麼 \(R(S_i) = R(S_k)\) 。
於是我們可以遞迴處理這個問題了。
程式碼
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
#define ll long long
#define ull unsigned long long
#define cint const int&
#define Pi acos(-1)
const int mod = 998244353;
const int inf_int = 0x7fffffff;
const ll inf_ll = 0x7fffffffffffffff;
const double ept = 1e-9;
int n;
int a[200100];
int dfs(cint l, cint r, cint num, int key) {
if(l > r) return 0;
if(l == r) return 1;
int k = lower_bound(a+1, a+1+n, key+(1<<num)) - a;
int x = dfs(l, k-1, num-1, key);
int y = dfs(k, r, num-1, key+(1<<num));
if(!(x*y)) return max(x, y);
return max(x,y) + 1;
}
int main() {
ios::sync_with_stdio(false);
cin >> n;
for(int i=1; i<=n; i++) cin >> a[i];
sort(a+1, a+1+n);
int t=0;
while((1<<t) <= a[n]) ++t;
cout << n-dfs(1, n, t-1, 0);
return 0;
}