CF1446C Xor Tree
正难则反,遇到这种求最小删除数的题目,都可以转化为求最大保留数。
题目可以转化为:保留最多的数,使得只存在一对 \((i, j)\) 对于彼此而言 \(a_i \oplus a_j\) 的值都是最小的。(也就是有且仅有一条重边)
建立 \(01trie\),记 \(f_u\) 表示以 \(u\) 为根的子树内最多可以保留多少个数。
转移分为以下三种情况:
1、\(u\) 是叶子,则 \(f_u = 1\)
2、\(u\) 只有一个儿子 \(v\),则 \(f_u = f_v\)。
3、\(u\) 有两个儿子。下面主要考虑这个怎么做。
注意到一个重要性质:由于 $01trie $ 是从高位到低位建立的,因此子树内的数之间异或一定小于子树之间异或的结果。所以如果选择保留一棵子树的数,那么另一棵子树只能保留一个数。(若保留两个以上则又会产生新的重边)
所以 \(f_u = \max(f_{v1}, f_{v2}) + 1\)。
于是 \(dfs\) 一遍就做完了,时间复杂度 \(O(n)\),数组记得开大点,像 \(trie\) 这种建议至少开 \(10\) 倍空间。
想了一下还有一个顾虑是很多个环怎么办,但其实考虑一下连边的方式,一棵子树相当于一个连通块,因为不断有另一棵子树(也就是另一个集合)的点接进来,所以不会产生环。
#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l); i <= (r); ++ i)
#define G(i,r,l) for(int i(r); i >= (l); -- i)
using namespace std;
using ll = long long;
const int N = 2e6;
int n, cnt;
int a[N], ch[N * 2][2], f[N * 2];
void insert(int x){
int u = 0;
G(i, 30, 0){
int v = ((x >> i) & 1);
if(!ch[u][v]) ch[u][v] = ++ cnt;
u = ch[u][v];
}
}
void dfs(int u){
int v1 = ch[u][0], v2 = ch[u][1];
if(v1 && v2){
dfs(v1);
dfs(v2);
f[u] = max(f[v1], f[v2]) + 1;
}
else if(v1 && !v2){
dfs(v1);
f[u] = f[v1];
}
else if(!v1 && v2){
dfs(v2);
f[u] = f[v2];
}
else{
f[u] = 1;
}
// printf("u = %d\tf[%d] = %d\n", u, u, f[u]);
return ;
}
void Main(){
cnt = 0;
cin >> n;
// F(i, 0, cnt){
// f[i] = 0;
// ch[i][0] = ch[i][1] = 0;
// }
F(i, 1, n){
cin >> a[i];
insert(a[i]);
}
dfs(0);
cout << n - f[0] << '\n';
return ;
}
signed main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
int T = 1;
while(T --) Main();
return fflush(0), 0;
}

浙公网安备 33010602011771号