XOR
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1841 Accepted Submission(s): 586
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.
2 2 1 2 4 1 2 3 4 3 1 2 3 5 1 2 3 4 5
Case #1: 1 2 3 -1 Case #2: 0 1 2 3 -1HintIf 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个数中任选m(m>=1)个数异或起来,求所有可能的异或和的第k小值。
这题传说是什么线性基之类的,但实际并不完全是基,因为基的概念是两两内积为0.
下面说题解:
简单来说,我有a和b两个数,假设(a,b)是a和b能够异或出来的所有值,当然这里(a,b)={a,b,a^b},可能要去重,其实我们不用关注具体是多少,只要知道用(a,b)表示就行了,那么我们有结论(a,b)=(a^b,b)=(a^b,a)=(a,b,a^b)等等,同时还有交换律(a,b)=(b,a),我整一个简单的吧(a,b)=(a^b,b),证明两个集合相等,显然是证互相包含了,首先a和b可以变出a^b,这样我们就可以利用a^b和b来生成其他数,于是(a,b)是包含(a^b,b)的,同理a^b和b可以先变出a^b^b=a,于是(a^b,b)包含(a,b),因此有(a,b)=(a^b,b),其他同理可证.
这里做推广到(a1,a2,a3,....,an),比如b1=a1^a2,那么保留至少一个a1或a2,我们有(b1,a2,a3,...,an)=(a1,a2,a3,...,an),以此我们可以一直做下去...甚至完全变成(b1,b2,b3,...,bn).这是个什么过程呢?很明显高斯消元吗.
既然(a1,a2,a3,...,an)=(b1,b2,b3,...,bn),那么我们想方设法让b1,b2,b3,...,bn尽量变成基,也就是理想情况下两两内积为0.我们考虑将a1,...,an写成二进制形式,而异或等价于模2加法。
于是a1,..,an所能表示的所有数的形式为c=(a1*x1)^(a2*x2)^....^(an*xn),x1,x2,...xn取{0,1},xi表示ai这个数取不取,我们按位考虑,因为所有ai最多60位,因此c[i]=(a1[i]*x1+a2[i]*x2+...+an[i]*xn)%2; c[i]表示c的二进制表示第i位,ak[i]同理.
这样我们也成矩阵形式
(a1[0],a2[0],...,an[0]) (x[1]) (c[0])
(a1[1],a2[1],...,an[1]) (x[2]) (c[1])
(................................) (*****) %2= (*****)
(.....................................) (*****) (*****)
(a1[60],a2[60],...,an[60]) (x[n]) (c[60])
实际并不是这么做的,我们要将矩阵转置过来
(a1[0],a1[1],...,a1[60]) (a1[0]) (a2[0]) (an[0]) (%2) (c[0])
(a2[0],a2[1],...,a2[60]) (a1[1]) (a2[1]) (an[1]) (%2) (c[1])
(.................................) 然后写成这种形式x1 (........) + x2 (........) + xn (........) (......) = (......)
(.................................) (........) (.........) (........) (......) (......)
(an[0],an[1],...,an[60]) (a1[60]) (a2[60]) (an[60]) (%2) (c[n])
这样基的形式就很明显,于是我们对转置矩阵进行高斯消元,理想情况是消成对角型矩阵。
(1,0,0,0,0)
(0,1,0,0,0)
(0,0,1,0,0)
(0,0,0,1,0)
(0,0,0,0,1)
这样是真正的两两内积为0,这样当所有xi遍历{0,1},将得到2^n-1种结果,-1是因为所有xi不能同时取0,因为题目条件限制,必须取m(m>=1)个数异或.不理想情况呢?
(1,0,0,0,0)
(0,1,0,0,1)
(0,0,1,0,0)
(0,0,0,1,1)
列数多的情况
(1,0,1,0,0)
(0,1,1,0,1)
(0,0,0,1,1)
(0,0,0,0,0)
消不成对角型的情况这些列可能会有多个1,但是实际并不影响
比如我们看这个矩阵
(1,0,1,0,0)
(0,1,1,0,1)
(0,0,0,1,1)
(0,0,0,0,0)
我们看前两列(从左到右是高位->低位),可以组成(11),(10),(01),(00),第1位为1对应了x1=1,第2位1对应了x2=1,他们之所以可以取遍2^2=4种可能,是因为他们是基,基是独立的,那么第3位呢?对角上没有1啊,这样其实第3位的值是取决于前2位的,也就是固定的,比如(11)第三位必然是0,因为(1,1)代表x1=x2=1,也就是第1,2行都是取的,由于第1,2行的第3列都是1,因此异或起来为0(这个取决于奇偶性了),于是第3位是确定的,因此可以得到(110),(101),(011),(000),由于第4位又是对角上的1(位置并不一定在对角线,但1所在的行这个1是第一次出现,其实就是一个行阶梯型),显然又是独立的,也就是x3可取0和1
x3=1,得到(1101),(1011),(0111),(0001)
x3=0,得到(1100),(1010),(0110),(0000)
可以发现每遇到对角上一个1,能表示的数就翻倍了。
看第5列,由于是多出来的,对角没有元素了,同样的第5位是前4位决定的,如(1101)表示x1=x2=x3=1,那么第5位就是第2,3行第5列的1异或起来,也就是(11010)。
说了一堆,也就是所能表示的数的个数就是pow(2,行阶梯型的对角1的个数),注意0的问题,如果不能取0,则需-1,能不能取0,取决于最后一行是不是全0,若是的话,则取x1=x2=...=xn-1=0,xn=1,就能得到0,否则得不到0.
说了一堆,看出了类似线性基的样子,并且可以发现从第1列到最后一列的扫描过程,生成的数字是排好序的,恰好可以发现第k大,如果能取0,则k=k-1,那么k的二进制表示刚好对应了线性基的取法,例如k=(1101),则表示取x1=x2=x4=1,x3=0这个对照上面的扫描过程理解一下吧,不详细解释了,也比较难解释清楚!
提交一直WA,各种调试对拍,最后发现一个神奇的BUG,见代码。
代码:
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<vector>
#include<map>
#define Maxn 10010
#define ll long long
using namespace std;
ll a[Maxn];
int d;
void gauss(int n){
int r=1,i,j;
for(i=60;i>=0;i--){
if(!(1LL<<i&a[r])){
for(j=r+1;j<=n;j++)
if(1LL<<i&a[j]) break;
if(j>n) continue;
swap(a[r],a[j]);
}
for(j=1;j<=n;j++){
if(j==r||!(1LL<<i&a[j])) continue;
a[j]^=a[r];
}
r++;
if(r==n+1) break; //BUG:r=n+1会利用前一组数据,需要break
}
d=r-1;
}
ll query(ll x,int n){
x-=d!=n;
if(!x) return 0;
if(x>=(1LL<<d)) return -1;
ll ans=0;
for(int i=d-1;i>=0;i--)
if(x>>i&1) ans^=a[d-i];
return ans;
}
int main()
{
int t,n,m,cas=1;
ll x;
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)
scanf("%I64d",a+i);
gauss(n);
cin>>m;
printf("Case #%d:\n",cas++);
while(m--){
scanf("%I64d",&x);
printf("%I64d\n",query(x,n));
}
}
return 0;
}