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

相关文章:

  • 2021-09-22
  • 2021-10-28
  • 2022-12-23
  • 2021-05-20
  • 2021-06-14
  • 2022-01-21
  • 2022-12-23
猜你喜欢
  • 2021-06-22
  • 2021-07-27
  • 2022-01-19
  • 2022-02-27
  • 2021-10-03
  • 2021-09-01
  • 2021-07-10
相关资源
相似解决方案