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;
}