【HDU 3949】 XOR (线性基,高斯消元)

XOR

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 2302    Accepted Submission(s): 783

Problem Description
XOR is a kind of bit operator, we define that as follow: for two binary base number A and B, let C=A XOR B, then for each bit of C, we can get its value by check the digit of corresponding position in A and B. And for each digit, 1 XOR 1 = 0, 1 XOR 0 = 1, 0 XOR 1 = 1, 0 XOR 0 = 0. And we simply write this operator as ^, like 3 ^ 1 = 2,4 ^ 3 = 7. XOR is an amazing operator and this is a question about XOR. We can choose several numbers and do XOR operatorion to them one by one, then we get another number. For example, if we choose 2,3 and 4, we can get 2^3^4=5. Now, you are given N numbers, and you can choose some of them(even a single number) to do XOR on them, and you can get many different numbers. Now I want you tell me which number is the K-th smallest number among them.
Input
First line of the input is a single integer T(T<=30), indicates there are T test cases.
For each test case, the first line is an integer N(1<=N<=10000), the number of numbers below. The second line contains N integers (each number is between 1 and 10^18). The third line is a number Q(1<=Q<=10000), the number of queries. The fourth line contains Q numbers(each number is between 1 and 10^18) K1,K2,......KQ.
Output
For each test case,first output Case #C: in a single line,C means the number of the test case which is from 1 to T. Then for each query, you should output a single line contains the Ki-th smallest number in them, if there are less than Ki different numbers, output -1.
Sample Input
2
2
1 2
4
1 2 3 4
3
1 2 3
5
1 2 3 4 5
Sample Output
Case #1:
1
2
3
-1
Case #2:
0
1
2
3
-1
Hint

If you choose a single number, the result you get is the number you choose.
Using long long instead of int because of the result may exceed 2^31-1.

 
【题意】
  给n个数,可以在其中选任意个数异或,求所有异或出来的答案中的第k个。
 
【分析】
  

  求线性基?好像很厉害的东西..
  证明的话应该要用到X^Y=Z -> X^Z=Y这个性质
  线性基的相互异或的值可以完全代替原来的n个数的异或的值,这样数从n个变成了二进制位数个,数减少了很多这是一个优点。
  另外,像高斯消元一样求线性基的话,如果某一位上有代表这一位的1,那么其他位置上一定为0。(这个性质好像很好用,因为这样可以看作这个数是对这一位作唯一贡献的,这样求很多东西都很方便了) 所以感觉异或版的高斯消元跟普通的高斯消还是很不一样的,多了好性质啊..

  放一下让我看懂的解释吧~

1、线性基:

  若干数的线性基是一组数a1,a2,...an,其中ax的最高位的1在第x位。通过线性基中元素xor出的数的值域与原来的数xor出数的值域相同。

2、线性基的构造法:

  对每一个数p从高位到低位扫,扫到第x位为1时,若ax不存在,则ax=p并结束此数的扫描,否则令p=p xor ax。

3、查询:

  用线性基求这组数xor出的最大值:从高往低扫ax,若异或上ax使答案变大,则异或。

4、判断:

  用线性基求一个数能否被xor出:从高到低,对该数每个是1的位置x,将这个数异或上ax(注意异或后这个数为1的位置和原数就不一样了),若最终变为0,则可被异或出。需要特判0(在构造过程中看是否有p变为0即可)。例子:(11111,10001)的线性基是a5=11111,a4=01110,要判断11111能否被xor出,11111 xor a5=0,则这个数后来就没有是1的位置了,最终得到结果为0,说明11111能被xor出。

个人谈一谈对线性基的理解:

  很多情况下,只有有关异或运算和求最值,就可以用到线性       基。线性基有很多很好的性质,比如说如果有很多个数,我们可以构出这些数的线性基,那么这个线性基可以通过互相xor,能够构出原来的数可以相互xor构出的所有的数。所以可以大大减少判断的时间和次数。同时线性基的任何一个非空子集都不会使得其xor和为0,证明也很简单,反证法就可以说明。这个性质在很多题目中可以保证算法合法性,比如:BZOJ2460。

  构造的方法有点像贪心,从大到小保证高位更大。也比较好理解。就是这几行代码:

  for(int i=1;i<=n;i++) {

    for(int j=62;j>=0;j--) {

     if(!(a[i]>>j)) continue;//对线性基的这一位没有贡献

       if(!p[j]) { p[j]=a[i]; break; }//选入线性基中

       a[i]^=p[j];

     }

   }

  可以把n个数变成只有二进制位数那么多的数,这就是线性基的优秀之处。

  查询的话,也是一个贪心思想,如果可以使得ans更大,就把这一位的基xor进ans。

  for(int i=62;i>=0;i--) if((ans^p[i])>ans) ans=ans^p[i];//从线性基中得到最大值

  这就是线性基的基本用法和个人的一些理解。

转自:http://www.cnblogs.com/ljh2000-jump/p/5869991.html

代码如下:

 #include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#include<cmath>
using namespace std;
#define Maxn 10010
#define LL long long LL a[Maxn],num[];
int n,cnt; void gauss()
{
int l=,r=;
LL t;
while(l<=n&&r>=)
{
if(!(a[l]&(1LL<<r-)))
{
for(int i=l+;i<=n;i++) if(a[i]&(1LL<<r-))
{
t=a[l],a[l]=a[i],a[i]=t;
break;
}
}
if(!(a[l]&(1LL<<r-)))
{
r--;continue;
}
num[r]=l;
for(int i=;i<=n;i++) if(i!=l&&(a[i]&(1LL<<r-))) a[i]^=a[l];
l++;r--;
}
} void init()
{
scanf("%d",&n);
for(int i=;i<=n;i++) scanf("%lld",&a[i]);
memset(num,,sizeof(num));
gauss();
for(int i=;i<=;i++) if(num[i]!=) num[i]=a[num[i]];
cnt=;
for(int i=;i<=;i++) if(num[i]!=) cnt++;
} int main()
{
int T,kase=;
scanf("%d",&T);
while(T--)
{
init();
printf("Case #%d:\n",++kase);
int q;
scanf("%d",&q);
while(q--)
{
LL k;
scanf("%lld",&k);
if(cnt==n) k++;//zero
if(k>(1LL<<cnt)) {printf("-1\n");continue;}
int now=cnt;
LL ans=;
for(int i=;i>=;i--) if(num[i]!=)
{
LL x=(1LL<<now-);
if(k>x) ans^=num[i],k-=x;
now--;
}
printf("%lld\n",ans);
}
}
return ;
}

[HDU3969]

2016-09-30 15:03:05

上一篇:如何在C#中使用OpenCV(GOCW使用教程)


下一篇:Codevs 2611 观光旅游(floyed最小环)