codeforces-#683-E

codeforces-#683-E

十二月 03, 2020

C. Xor Tree

  • 题意

给出n (1e5)个点,每个点有一个值ai,对于每个点,它将找到其余所有点中与其异或和最小的点,并且与之连上双向边,问最少要在给出数组中删掉多少数才能使得剩余的数经过这样的操作能变成一颗树。

  • 题解

位运算题目不变的套路就是要想到 按位运算

根据题目中两点建立边的条件 需要想到

两个数的最高位 不同 时 才有可能建立联系

所以 按二进制最高位到最低位 逐个 分组

对于第 i 位 为0 的数分成 一组 为1的分成 一组

同一组的数 之间可能建立联系(边) 但具体怎么建 需要继续递归到下一位继续分组 才能看出来

而对于当前情况 若想最终剩余的点能构成数 则需要将 其中一个集合的数 删掉只剩1个

比如 某种情况下 几个数的二进制为:

{1xxxx ,1xxxx,1xxxx},{0xxxx,0xxxx}

根据最高位分组后 需要将某个集合的数删去只剩1个

才能迫使他从另一个集合中寻找 和他异或值最小的数建立边

同时这样递归分组也说明了 不会导致多个数之间成环的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
#define Turnoff std::ios::sync_with_stdio(false)
//#define P pair<ll,ll>
const int Max=2e5+5;
const int Mod=1e9+7;
const int inf=0x3f3f3f3f;

#define __DEBUG__
#ifdef __DEBUG__
#define DeBug(format, ...) \
{ \
fprintf(stdout, "[%s:%d]" format "", \
__FUNCTION__ , __LINE__, ##__VA_ARGS__); \
}
#else
#define DeBug(format,...)
#endif

vector<ll> arr;
int n,cnt=0;
int dfs(vector<ll>num,ll bios){
//cnt++;
//if(cnt>20)return 0;
if(!bios||num.empty())return 0;
vector<ll>zero,one;
for(auto i:num){
if(bios&i)one.push_back(i);
else zero.push_back(i);
}
int ans=min(max(0,(int)one.size()-1)+dfs(zero,bios>>1),max(0,(int)zero.size()-1)+dfs(one,bios>>1));
return ans;
}
int main(void){
Turnoff;
cin>>n;int ans=0;
for(int i=1;i<=n;i++){
ll num;cin>>num;
arr.push_back(num);
}
ans=dfs(arr,1ll<<32);
cout<<ans<<endl;
}