首页 > 代码库 > 异或最大
异或最大
今天可爱的Mayuyu会带领大家来学习一个东西,那就是异或最大,Mayuyu的问题描述如下。
题目:给定一个数组a[],再给出m个询问,每个询问一个数x,在a[]中找出一个数y,使得x与y的异或值最大。
分析:最直观的思路就是对于每一个询问,直接暴力在数组a[]中比较,找最大的,但这样做的时间复杂度会很大。
我们有一个很好的解法,那就是字典树,假设所有的数字范围均在int内,那么就可以建立深度为32的字典树
即可,所以总的时间复杂度为O(32*m)。
建好字典树后,从根节点往下遍历一遍就行了,先对x按位取反,尽量走相同的节点,如果不存在就忽略。
代码:
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; typedef long long LL; class Trie { public: Trie *next[2]; Trie() { memset(next,NULL,sizeof(next)); } }; Trie *root; void Insert(LL n) { Trie *p = root; for(int i=31;i>=0;i--) { int id = (n >> i) & 1; if(p->next[id] == NULL) p->next[id] = new Trie(); p = p->next[id]; } } void Delete(Trie *T) { if(T == NULL) return; for(int i=0;i<2;i++) if(T->next[i] != NULL) Delete(T->next[i]); } LL Match(LL m) { m = ~m; LL ans = 0; Trie *p = root; for(int i=31;i>=0;i--) { ans <<= 1; int bit = (m >> i) & 1; if(bit) { if(p->next[1]) { p = p->next[1]; ans++; } else { p = p->next[0]; } } else { if(p->next[0]) { p = p->next[0]; } else { p = p->next[1]; ans++; } } } return ans; } int main() { int T, tt = 1; scanf("%d",&T); while(T--) { int n,m; root = new Trie(); scanf("%d%d",&n,&m); for(int i=0;i<n;i++) { LL val; scanf("%I64d",&val); Insert(val); } printf("Case #%d:\n",tt++); while(m--) { LL val; scanf("%I64d",&val); printf("%I64d\n",Match(val)); } Delete(root); } return 0; }
题目:http://acm.csu.edu.cn/OnlineJudge/problem.php?id=1216
题意:给定一个数组,在这个数组中找出两个数,使它们的异或值最大。
代码:
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; const int N = 100005; typedef long long LL; const LL INF = (LL)1<<61; int a[N]; class Trie { public: Trie *next[2]; Trie() { memset(next,NULL,sizeof(next)); } }; Trie *root; void Insert(LL n) { Trie *p = root; for(int i=31;i>=0;i--) { int id = (n >> i) & 1; if(p->next[id] == NULL) p->next[id] = new Trie(); p = p->next[id]; } } void Delete(Trie *T) { if(T == NULL) return; for(int i=0;i<2;i++) if(T->next[i] != NULL) Delete(T->next[i]); } LL Match(LL m) { m = ~m; LL ans = 0; Trie *p = root; for(int i=31;i>=0;i--) { ans <<= 1; int bit = (m >> i) & 1; if(bit) { if(p->next[1]) { p = p->next[1]; ans++; } else { p = p->next[0]; } } else { if(p->next[0]) { p = p->next[0]; ans++; } else { p = p->next[1]; } } } return ans; } int main() { int n; while(scanf("%d",&n)!=EOF) { root = new Trie(); for(int i=0;i<n;i++) { scanf("%d",&a[i]); Insert(a[i]); } LL ans = -INF; for(int i=0;i<n;i++) ans = max(ans,Match(a[i])); printf("%lld\n",ans); Delete(root); } return 0; }
题目:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=1717
题意:给定一个数组a[],在这个数组中选择一个前缀和一个后缀,使它们的异或值最大,前缀与后缀不能交叉。
分析:Mayuyu明白一点,两个相同值异或后为0,那么所以以1 2 4 8 16为例说明,1 2 4 8是a[]的一个前缀
而4 8 16是a[]的一个后缀,但是它们有交叉,由异或的性质,它们实际上与前缀1 2和后缀16等价,因为
中间的相同部分抵消了。这样我们先对所有前缀插入深度为40的字典树,然后对于每一个后缀来查找,求出
最大值即可。
代码:
#include <iostream> #include <string.h> #include <stdio.h> using namespace std; const int N = 100005; typedef long long LL; const LL INF = (LL)1<<61; LL a[N]; class Trie { public: Trie *next[2]; Trie() { memset(next,NULL,sizeof(next)); } }; Trie *root; void Insert(LL n) { Trie *p = root; for(int i=40;i>=0;i--) { int id = (n >> i) & 1; if(p->next[id] == NULL) p->next[id] = new Trie(); p = p->next[id]; } } void Delete(Trie *T) { if(T == NULL) return; for(int i=0;i<2;i++) if(T->next[i] != NULL) Delete(T->next[i]); } LL Match(LL m) { m = ~m; LL ans = 0; Trie *p = root; for(int i=40;i>=0;i--) { ans <<= 1; int bit = (m >> i) & 1; if(bit) { if(p->next[1]) { p = p->next[1]; ans++; } else { p = p->next[0]; } } else { if(p->next[0]) { p = p->next[0]; ans++; } else { p = p->next[1]; } } } return ans; } int main() { int n; while(scanf("%d",&n)!=EOF) { root = new Trie(); LL tmp = 0; for(int i=0;i<n;i++) { scanf("%lld",&a[i]); tmp ^= a[i]; Insert(tmp); } tmp = 0; LL ans = -INF; for(int i=0;i<n;i++) { tmp ^= a[n-1-i]; ans = max(ans,Match(tmp)); } printf("%lld\n",ans); Delete(root); } return 0; }