本文主要是介绍趣味XOR,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
sgu275
友情链接
1、题意:给出n个数,选出来一个子集,使得集合的异或最大!
思路:高斯消元,将给出来的数写成一行二进制,依次排下来,如题目给的排成一个矩阵如下
1011
1001
0101
然后去消元,在消元的过程中保证当前的位置的左下角矩阵全为零,即设置一个变量in表示消元到第几列,如果当前列找不到非零的元素,那么可以调到下一列,行数不变。然后消元一直到 "行数>=n或者列数>=m"停止,这里假设行数是n,列数是m,保证消元过的每一列最多只有一个1,然后最后把每一列的元素异或起来,并写成二进制,这个就是异或和最大。
上面消元的矩阵过程如下如下
1011
0010
0101
->
1011
0101
0010
->用最后一行去消元第一行
1001
0101
0010
然后就是所有列异或得到1110(2)=14(10)
ps:假入这里要求我们求出异或出来使得最大值的是哪些数,我们可以在原来矩阵的右边并上一个n行n列的单位矩阵,消元出来之后也是跟着所有列异或,得到一个二进制串,从左到右分别的第i个位分别表示选不选第i个数,然后这个二进制串就是所要求的集合的二进制表示了!
int n,m,cnt;
int a[MM][MM];
int find(int x,int in)
{for(int i=x;i<n;i++)if(a[i][in])return i;return -1;
}
void solve()
{long long x,ans=0;memset(a,0,sizeof(a));for(int i=0;i<n;i++){cin>>x;cnt=0;while(x>0){a[i][cnt++]=x&1;x>>=1;}m=max(m,cnt);}for(int i=0;i<n;i++){for(int j=0;j<m/2;j++)swap(a[i][j],a[i][m-j-1]);}//到这里都是数据的处理,即转化成矩阵for(int i=0,in=0;i<n&&in<m;i++,in++){int x;while((x=find(i,in))==-1&&in<m)in++;//找到一列有非零元素的列if(in==m)break;//找不到非零列,终止if(x!=i)for(int j=0;j<m;j++)swap(a[i][j],a[x][j]);//将非零元素提到当前行for(int j=i-1;j>=0;j--)if(a[j][in]){//当前行向下消元for(int k=0;k<m;k++)a[j][k]^=a[i][k];}for(int j=i+1;j<n;j++)if(a[j][in]){//当前行向上消元for(int k=0;k<m;k++)a[j][k]^=a[i][k];}}for(int j=0;j<m;j++)//全部列异或{int t=0;for(int i=0;i<n;i++)t^=a[i][j];if(t)ans+=1LL<<(m-j-1);}cout<<ans<<endl;
}
int main()
{while(~scanf("%d",&n))solve();return 0;
}
贴一份莫涛的代码,简洁时间复杂度也低O(n*64),减少了消元的过程,把可以用已经有的数凑出来的数去掉,最后消元的行数剩下63,这63能生成所给的数据,然后对这63行进行异或,再用压缩优化,使复杂度降低到O(n*64)
#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
void MAX_XOR(int n)
{long long base[64];memset(base,0,sizeof(base));for(int k=0;k<n;k++){long long x;scanf("%lld",&x);for(int i=62;i>=0;i--){if((x>>i)&1){if(base[i]==0){base[i]=x;break;}else x^=base[i];}}}for(int i=0;i<63;i++){for(int j=i+1;j<63;j++){if((base[j]>>i)&1){base[j]^=base[i];}}}long long ans=0;for(int i=0;i<63;i++)ans^=base[i];printf("%lld\n",ans);
}
int main()
{int n;scanf("%d",&n);MAX_XOR(n);return 0;
}
2、另一类XOR问题
Zeus 和 Prometheus 做了一个游戏,Prometheus 给 Zeus 一个集合,集合中包含了N个正整数,随后 Prometheus 将向 Zeus 发起M次询问,每次询问中包含一个正整数 S ,之后 Zeus 需要在集合当中找出一个正整数 K ,使得 K 与 S 的异或结果最大。
友情链接
对n个数建立一颗二进制前缀树,然后贪心在树上找数就行了,代码如下
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int cnt=1;
long long dp[5100000][2];
void insert(int root,long long x,int now)
{if(now<0)return ;long long &ans=dp[root][(x>>now)&1];if(ans==-1)ans=cnt++;insert(ans,x,now-1);
}
long long query(int root,long long x,long long ans,int now)
{if(now<0)return ans;long long &temp=dp[root][((x>>now)&1)^1];if(temp==-1)query(dp[root][(x>>now)&1],x,ans*2+((x>>now)&1),now-1);else query(temp,x,ans*2+(((x>>now)&1)^1),now-1);
}
int main()
{int T,Case=1;scanf("%d",&T);while(T--){cnt=1;int n,m;scanf("%d%d",&n,&m);memset(dp,-1,sizeof(dp));for(int i=0;i<n;i++){long long x;scanf("%I64d",&x);insert(0,x,32);}printf("Case #%d:\n",Case++);for(int i=0;i<m;i++){long long x;scanf("%I64d",&x);printf("%I64d\n",query(0,x,0,32));}}return 0;
}
3、给定一排数,求连续的子串使得异或和最大。
做法:令F[i,j],表示下标是i的到下标是j的数的异或和,根据异或的性质我们可以知道F[i,j]=F[1,i-1] XOR F[1,j],也就是在前缀F中找到一个数t,使得t XOR F[1,j]最大,这样可以转化成第二类问题
题目:LA 4682 - XOR Sum
友情链接:点击打开
以下是我的丑陋的代码:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int cnt=1;
long long dp[4100000][2];
void insert(int root,long long x,int now)
{if(now<0)return ;long long &ans=dp[root][(x>>now)&1];if(ans==-1)ans=cnt++;insert(ans,x,now-1);
}
long long query(int root,long long x,long long ans,int now)
{if(now<0)return ans;long long &temp=dp[root][((x>>now)&1)^1];if(temp==-1)query(dp[root][(x>>now)&1],x,ans*2+((x>>now)&1),now-1);else query(temp,x,ans*2+(((x>>now)&1)^1),now-1);
}
int main()
{int T;scanf("%d",&T);while(T--){cnt=1;int n,m;long long ans=0,now=0;scanf("%d",&n);memset(dp,-1,sizeof(dp));for(int i=0;i<n;i++){long long x;scanf("%lld",&x);insert(0,now,33);now^=x;ans=max(ans,now^query(0,now,0,33));}cout<<ans<<endl;}return 0;
}
4、给定一个有n个数的数组,求这个数组的子串异或和小于k的个数
思路:同样用上面的方法,只不过在每个节点上面记录有多少个数要经过这个节点,当低位无论怎么异或都无法大于k时,这个时候返回记录的数就可以了,然后一直向下统计就是答案了
友情链接:点击打开链接
丑陋代码如下:
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int cnt=1;
int dp[4100000][2];
int num[4100000][2];
void insert(int root,int x,int now)
{if(now<0)return ;num[root][(x>>now)&1]++;int &ans=dp[root][(x>>now)&1];if(ans==-1)ans=cnt++;insert(ans,x,now-1);
}
///返回当前树节点值和x的异或和比k小的个数
int query(int root,int x,int ans,int now,int k,int fa)
{int res=0;if(ans>=k)return 0;///重要的剪枝int first=(x>>now)&1,second=((x>>now)&1)^1;if(now<0)return (ans<k?fa:0);if(dp[root][first]!=-1){if(ans+(1<<now+1)<k)res+=num[root][first];///无论后面的异或情况如何,异或出来永远小于kelse res+=query(dp[root][first],x,ans,now-1,k,num[root][first]);}if(dp[root][second]!=-1){if(ans+(1<<now+1)<k)res+=num[root][second];///无论后面的异或情况如何,异或出来永远小于kelse res+=query(dp[root][second],x,ans+(1<<now),now-1,k,num[root][second]);}return res;
}
int main()
{int T;scanf("%d",&T);while(T--){cnt=1;int n,k,now=0;memset(dp,-1,sizeof(dp));memset(num,0,sizeof(num));///对每个节点计数scanf("%d%d",&n,&k);long long ans=0;for(int i=0;i<n;i++){int x;scanf("%d",&x);insert(0,now,25);now^=x;ans+=query(0,now,0,25,k,0);}cout<<ans<<endl;}return 0;
}
这篇关于趣味XOR的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!