Description
给定 \(n\) 个自然数,每个数会与自己异或最小的数,向它连一条边。求至少删去多少个数,才能使这个自然数集合经过这样的操作构成的图是一棵树。边都是无向的,重边自动合并。
Solution
考虑在 0-1 Trie 中,如果结点 \(p\) 的子树中有 \(1\) 个有效叶子结点,那么这个结点一定会与子树外的结点连边,因此我们可以说 \(p\) 的子树是安全的。
相反地,如果 \(p\) 的子树内有 \(\ge 2\) 个有效叶子结点,那么这些结点之间一定会相互匹配连边,因此 \(p\) 子树内的这些节点就会与剩余的部分断开。
因此,对于一个结点 \(p\),它的左子树和右子树分别为 \(x,y\),如果两边的叶子结点数目都大于 \(1\),那么我们需要将一边的有效叶子结点数目删减到不超过 \(1\),才能使得整个过程是有效的。至于删除哪一边,需要递归做下去。
具体地,我们定义 \(f(p)\) 表示结点 \(p\) 的子树内的有效叶子结点最多可以保留多少个。
如果 \(p\) 的左子树中的有效叶子结点个数为零,那么 \(f(p) = f(p.rightChild)\)。
如果 \(p\) 的右子树中的有效叶子结点个数为零,那么 \(f(p) = f(p.leftChild)\)。
如果 \(p\) 的左右子树中的有效叶子结点个数都非零,那么 \(f(p) = \max(f(p.leftChild), p.(rightChild)) + 1\),即我们将一边删除到只剩下一个。
作为递归的边界,当子树内只有一个结点(或者层数达到叶子)的时候,返回一个值即可。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1000005;
int n;
vector<int> a;
int f(vector<int> a, int b)
{
if (b == -1)
return a.size() ? 1 : 0;
vector<int> c[2];
for (int i : a)
c[(i >> b) & 1].push_back(i);
if (c[0].size() == 0)
return f(c[1], b - 1);
if (c[1].size() == 0)
return f(c[0], b - 1);
return max(f(c[0], b - 1), f(c[1], b - 1)) + 1;
}
signed main()
{
ios::sync_with_stdio(false);
cin >> n;
for (int i = 1; i <= n; i++)
{
int t;
cin >> t;
a.push_back(t);
}
cout << n - f(a, 30) << endl;
}