codevs 3031:最富有的人
题目描述 Description 在你的面前有n堆金子,你只能取走其中的两堆,且总价值为这两堆金子的xor值,你想成为最富有的人,你就要有所选择。 输入描述 Input Description 第一行包含两个正整数n,表示有n堆金子。 第二行包含n个正整数,表示每堆金子的价值。 输出描述 Output Description 第一行包含一个正整数,表示能获得的最大总价值。 样例输入 Sample Input 10 1 2 3 4 5 6 7 8 9 10 样例输出 Sample Output 15 数据范围及提示 Data Size & Hint 数据范围: n<=100000 每堆金子数<=2^31-1
芒果君:这道题第一个难点在,你特么看不出它是trie树啊啊啊啊!
然后,就算你看出来了,不会位运算,也hin难搞。
过程描述起来很简单,就是把每个数字变成二进制从位数上限开始建立trie树,再逐个查询,尽量向“1”的方向走取最大更新答案。这里出现的几个位运算我弱弱的解释一下。
① (x>>i)&1 的作用是找到二进制下x从右到左的第i+1位,一个数“>>”右移i是除以2的x次方,“&”叫位与,同为1则为1,否则为0,用它可以找到二进制的每一位数,因为1&1=1,0&1=0;
② trie[p][t^1] “^”叫异或,不同为1相同为0,你可以理解为“不进位的加法”,0^0=0,0^1=1,1^1=0,而且我才发现这道题用它找路的奇妙性质。我们的贪心策略是尽量往1走,如果该位是0需要1变成1,1需要0变成1,但是你有木有发现,它需要的“路”其实就是该位的数字与1异或的结果!
③total|=(1<<i) 左移“<<”就是乘2的i次方,“|”位或,只要有一个是1就是1,不然是0,它在这里可以强行把那一位改成1;
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 #define maxn 100010 5 using namespace std; 6 int trie[maxn<<6][3],cnt,n; 7 long long a[maxn],ans; 8 void insert(int x) 9 { 10 int p=0; 11 for(int i=30;i>=0;--i){ 12 int t=(x>>i)&1; 13 if(!trie[p][t]) p=trie[p][t]=++cnt; 14 else p=trie[p][t]; 15 } 16 } 17 void query(int x) 18 { 19 int p=0; 20 long long sum=0; 21 for(int i=30;i>=0;--i){ 22 int t=(x>>i)&1; 23 if(!trie[p][t^1]) p=trie[p][t]; 24 else{ 25 p=trie[p][t^1]; 26 sum|=(1<<i); 27 } 28 } 29 ans=max(ans,sum); 30 } 31 int main() 32 { 33 scanf("%d",&n); 34 for(int i=1;i<=n;++i){ 35 scanf("%lld",&a[i]); 36 insert(a[i]); 37 } 38 for(int i=1;i<=n;++i) query(a[i]); 39 printf("%lld\n",ans); 40 return 0; 41 }
(PS:好久没有这么认真的写过题解了……)