CF1446C 异或树
1 CF1446C 异或树
2 题目描述
时间限制 \(2s\) | 空间限制 \(256M\)
对于给定的非重复非负整数序列 \((b_1,b_2,...,b_k)\),我们用下面方法判断是不是完美序列:
- 构造一个有 \(k\) 个节点的图,节点的数字从 \(b_1\) 到 \(b_k\)。
- 对于每一个 \(i\) 从 \(1\) 到 \(k\):找到 \(j\) 满足 \(1≤𝑗≤𝑘,𝑗≠𝑖\) 所有的 \(j\) 都满足 \(b_i \oplus b_j\) 最小。下一步在节点 \(b_i\) 和 \(b_j\) 之间画出无向边。
- 如果用上述方法画出的图是一棵树,我们说这个序列是完美的。
有可能对于有些数字 \(b_i\) 和 \(b_j\),他们之间的边可能添加两次,但是只能添加一次。
给定一个非重复非负整数序列 \((a_1,a_2,...,a_n)\)。你可以删除一些元素(当然也可以不删除)让剩余的序列是完美序列。达到这个效果的最小删除次数是多少?
数据范围:\(2≤𝑛≤200,000\); \(0≤𝑎_𝑖≤10^9\)。
3 题解
我们发现,让两个数异或和尽量小这一类问题可以用字典树解决。容易发现,与一个数异或和最小的数一定在第一个有两个子节点的节点的另一棵子树中。也就是说,如果这棵树中某一个节点的两个子树都存在有两个子节点的节点,那么这个图最终构造出来就一定是一个森林,而不是一棵树,不符合题目要求。
那么为了让我们构造出来的图是一棵树,同时删除的节点数最小,我们选择删去两个子树中都存在两个子节点的节点的一部分节点,使这棵子树中不再存在有两个子节点的节点。我们可以计算留下来的节点数来推出答案。
通过 \(dfs\),我们可以计算出最终的答案:如果某个节点已经是叶子节点了,那么返回节点的个数 \(1\);如果某个节点的左右子树中有且仅有一个子树有节点,那么继续递归这个有节点的子树;而如果某个节点既有左子树,又有右子树,那么答案是两个子树的节点个数的最大值 \(+1\),意思就是两个子树中某一个子树不变,另一个子树只留下一个节点。在这种构造方法下再添加任意一个节点都会导致某一条边移动,整个图就不连通了。
4 代码(空格警告):
#include <iostream>
using namespace std;
const int N = 2e5+10;
int n, tot;
int a[N], tr[N*30][3];
void insert(int x)
{
int p = 0;
for (int i = 30; i >= 0; i--)
{
int c = (x >> i) & 1;
if (!tr[p][c]) tr[p][c] = ++tot;
p = tr[p][c];
}
}
int query(int p)
{
if (!tr[p][0] && !tr[p][1]) return 1;
if (!tr[p][0] && tr[p][1]) return query(tr[p][1]);
if (tr[p][0] && !tr[p][1]) return query(tr[p][0]);
return max(query(tr[p][0]), query(tr[p][1])) + 1;
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], insert(a[i]);
cout << n - query(0);
return 0;
}