[hdu3949][线性基]XOR

XOR

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

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.

Author
elfness

Source
2011 Multi-University Training Contest 11 - Host by UESTC

Recommend
xubiao | We have carefully selected several similar problems for you: 3946 3948 3947 3945 3944

线性基

在一个2维平面中,所有向量都可以被(0,1),(1,0)的向量表示出来,选定一个集合,如果集合中的某个向量能被另外的几个向量表示出来的话,这个集合就是线性相关的。而(0,1)(1,0)这个集合就是线性无关的。
线性无关的一系列向量有非常好的性质,因为这个空间内的所有向量都可以被这些向量表示出来。那么这些东西和线性基有什么关系呢?
线性基就是基于这个原理诞生的,他是用来处理一系列数的xor和问题的。
因为我们求解异或问题,比如一系列数能组成的k大xor和,这种东西如果用原本的数来做的话,容易枚举出相同的xor值,那就非常麻烦了。但是如果这个问题放在线性基下做的话就非常简单了。
我们把一个数二进制分解,他在二进制下的0/1代表的就是他所对应的向量。为了让一个数不被其他数表示出来,我们可以对这个矩阵高斯消元,那么1实际上就是我们保留下来的线性基中的基的向量表示(考虑对于一个1来说,要想表示出这个1,就需要用另一个1和0xor起来,如果我们消去其他的1,那么这个1就没法表示出来了,所以可以高消)。如果数是n个,列就是log,高消效率就是log*n*log,还可以接受。如果我们直接把一个数用ll存下来的话,甚至后面那个log还能省掉。但是因为是xor,所以我们可以使用一个更加不需要脑子的消元方法。
我们不考虑像高消那样,用一个基去消其他的数,而考虑用一个数去消基。我们给二进制每一个位置指定一个基,那么这个基的这一位一定是1,前面都是0,后面我们不关心(因为他会在后面被消掉或者不消掉,但是不影响)。那么一个数加入线性基的时候,如果他二进制的某一位上面是1,且这一位没有基,就把这个数当成这一位的基,否则把这个数xor上对应的基(这样就可以消掉这一位的1了)。再让对应的基消去当前对应的1,那么实现起来基本没什么细节。
为什么要用下面这种方法呢,因为有可能你的列数是比行数来的大的,比如ll下,列数为61,但是你的行数不够61,那你高消就要带着2个指针走,一个是当前行,一个是当前消的元,我觉得挺麻烦的,代表元的方法方便好写我觉得还行。

———————以上内容是我在做这题之前写的———————–
做完题之后给大家讲几个坑点,这也是导致我这题调了很久的原因。因为我是从高消上面考虑的,所以一个元素进去的时候,要先用基消他的1,然后再用他的1去消这些基(从高消的实现过程上考虑)。这样才能最终变成高消的对角线为1,后面随意的形式。网上的线性基多是不消成严格对角线为1的形式,两种线性基都好做最大xor值,简单贪心即可,但是我不知道那种线性基应该怎么去数k大或者k小,我问了一下身边的同学,他们说小讨论一下?我讨论不来,所以写了一个比较长的线性基板子。

sol:

线性基裸题。
每次询问这些数(至少一个,不能不选)能够组成的异或和中第 k 小的数是什么(去掉重复的异或和)。这个东西和我们线性基的用法可以说是一模一样了。
假设求第k小,线性基中的基从大到小是v0,v1,v2
显然就是 \sum_i ((k>>i)&1)*vi.
显然如果把基从小到大排序,v0,v1,v2,v3;vk提供2^k的方案。这个二进制考虑一下就行了。最后+-1什么的自行考虑即可。

#include<iostream>
#include<algorithm>
#include<string>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<cmath>
using namespace std;
typedef long long ll;
typedef double s64;
int n,m;
const int N=11000;
ll c[N],sta[80],Pow[80];
int b[80],top;
inline void solve()
{
    scanf("%d",&n);
    for(int i=1;i<=n;++i) scanf("%lld",&c[i]);
    memset(b,0,sizeof(b));
    int flag=0;
    for(int i=1;i<=n;++i)
    {
        int j;
        for(j=60;j>=0;--j)
        if((c[i]>>j)&1)
        {
            if(!b[j])
            {
                for(int k=60;k>=0;--k)
                if(b[k])
                    if((c[i]>>k)&1)
                    c[i]^=c[b[k]];
                for(int k=60;k>=0;--k)
                if(b[k])
                    if((c[b[k]]>>j)&1) 
                    c[b[k]]^=c[i];
                b[j]=i;
                break;
            }
            else c[i]^=c[b[j]];
        }
        if(j==-1) flag=1;
    }
    top=0;
    for(int i=0;i<=60;++i)
    if(b[i]) {sta[top++]=c[b[i]];}
    --top;
    scanf("%d",&m);
    for(int i=1;i<=m;++i)
    {
        ll x;
        scanf("%lld",&x);
        x-=flag;
        if(x>>top+1) {printf("-1\n");continue;}
        ll ans=0;
        for(int j=top;j>=0;--j)
        if((x>>j)&1) ans^=sta[j];
        printf("%lld\n",ans);
    }
}
int main()
{
//  freopen("3949.in","r",stdin);
//  freopen("3949.out","w",stdout);
    Pow[0]=1;
    for(int i=1;i<=60;++i) Pow[i]=Pow[i-1]<<1;
    int T;
    cin>>T;
    for(int i=1;i<=T;++i)
    {
        printf("Case #%d:\n",i);
        solve();
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值