CF1446C Xor Tree
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;
}