hdu 3949 Xor ( 线性基 解第k小子集异或和)

题目链接

题意: T组测试,每次测试给n个long long 型数字,做Q次询问第k小的子集异或和值。
  1. 线性基的异或和是无法进位,A[i] > all{ 0……i-1的所有线性基的异或组合},
    实际相当于二进制,求第k最小,实际求k的二进制表达中为1的项位置.
    (第i位不为0即对应于线性基中第i位不为0的基在要选的组合中。

  2. 必须注意的是: 线性基无法凑成子集异或和为0的情况。所以我们在建线性基组的时候,一定要判定是否在添加过程中,没有参与A中线性基的构建,直接被现有的基消为0,就表示有为0的情况。
    (我们的二进制遍历是专门设计解决第k小异或和不为0的子集异或和,所以有0的情况,就是找第k-1小异或和)

  3. 如果二进制完后,k不为0,即表示询问范围超出了结果的个数,输出-1。

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#include <algorithm>
#define llt long long
using namespace std;
const int Size=5*1e4+10;
const int L=60;
int have_0;
llt A[65];
void add_BaseLine(llt x){
    int i;
    for(i=L;i>=0;--i)
        if(x&(1ll<<i)){
            if(A[i]) x^=A[i];
            else {
                for(int j=i-1;j>=0;--j)
                    if(x&(1ll<<j)) x^=A[j];
                for(int j=i+1;j<=L;++j)
                    if(A[j]&(1ll<<i)) A[j]^=x;
                A[i]=x;
                return ;
            }
        }
    have_0=1;//x能够被现有的基线性表示;
}

llt solve(llt k){
    if(have_0) k--;
    llt ans=0;
    //二进制遍历组合
    for(int i=0,j=0;i<=L;++i){
        if(A[i]==0) continue;

        if(k&(1ll<<j)) {
            ans^=A[i]; k^=(1ll<<j);
        }
        ++j;
    }
    if(k) return -1;//输入k超出结果范围
    return ans;
}

int main(){
    int T,kase=0;
    scanf("%d",&T);
    while(T--){
        have_0=0;
        int N;scanf("%d",&N);
        memset(A,0,sizeof(A));
        while(N--){
            llt a;scanf("%lld",&a);
            add_BaseLine(a);
        }

        int Q;
        scanf("%d",&Q);
        printf("Case #%d:\n",++kase);
        while(Q--){
            llt k;
            scanf("%lld",&k);
            printf("%lld\n",solve(k));
        }
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_38786088/article/details/79915194
今日推荐