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\) 连一条无向边。 \(\oplus\) 代表异或
如果两个点互相连边只计算一条。
问你最少去掉几个点之后能让剩下的图为一颗树。
思路
好巧妙的题...
-
原图一定是树或森林,不可能出现环。
证:
不失一般性,如果存在环,我们将环取出,重新标号为 \(1,...,k\) ,规定 \(a\) 连向 \(a+1\) , \(k\) 连向 \(1\) 。
考虑 \(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\) 的位置分为两个集合 \(S_0, S_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;
}