Codeforces Round #683 (Div. 2, by Meet IT) E
大意
给你 (n) 个各不相同的数,写在 (n) 个点上。
记写在第 (i) 个点上的数为 (a_i) ,则对于任意点 (i) 会与使 (a_ioplus a_j) 最小化的点 (j) 连一条无向边。 (oplus) 代表异或
如果两个点互相连边只计算一条。
问你最少去掉几个点之后能让剩下的图为一颗树。
思路
好巧妙的题...
-
原图一定是树或森林,不可能出现环。
证:
不失一般性,如果存在环,我们将环取出,重新标号为 (1,...,k) ,规定 (a) 连向 (a+1) , (k) 连向 (1) 。
考虑 (1 ightarrow2) 的边,按照规定,因为有 (2 ightarrow 3) ,所以 $ a_2oplus a_3 < a_1oplus a_2$ 。
考虑 (3 ightarrow 4 ...) 显然最后有 (a_koplus a_1 < a_{k-1}oplus a_k < ... <a_1oplus a_2) 又 (a_1oplus a_2 < a_1oplus a_k) ,所以不难发现这个环是非法的。
也就是说按照题述规则链接不会出现环。
-
将 (a_i) 按照二进制下最大的 (1) 的位置分为两个集合 (S_0, S_1) 。
即 (exists k ; s_iin S_1,s_jin S_0 ;s_j<2^kleq s_i<2^{k+1})
可以发现此时若 (|S_0|>1 and |S_1|>1) 那么原图一定不是一棵树。
因为两个集合的点只会与和自己处于相同集合内的点连边。
因为最少删除就是最多保留。
所以我们最多只能让其中一个集合保留一个点,另外一个保留尽量多的点。
记 (R(S_i)) 为该集合最多保留的点, (S_j,S_k) 为 (S_i) 在上述分法下的子集。
按照上述分法,两个子集的最多可以保留的点的数量是互不影响的,因为它们之间在元素数量都大于一时不可能有边。
如果有一个集合元素数量等于一而另一个不为零,那么那一个元素肯定连向另一个集合里的元素,此时依然要保证另一个集合里的元素保留的尽量多。
如果一个集合为零,那么肯定不用考虑他了,另一个集合也要尽量保留的多。
那么当 (R(S_j) eq 0 and R(S_k) eq 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;
}