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\) 构成一个连通图,最少还需删掉多少数。那么:

\[f(S,\text{bit}) = \begin{cases} 0 && |S| < 3\\ \min(f(S_0,\text{bit} - 1) + \max(0,|S_1|-1),f(S_1,\text{bit} - 1) + \max(0,|S_0| - 1)) && |S|\geq 3 \end{cases} \]

注意:直接选 \(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;
}
posted @ 2020-11-19 22:06  duyiblue  阅读(507)  评论(4编辑  收藏  举报