2019牛客暑期多校训练营(第一场)H 线性基+计算贡献

题意

给n个整数,求满足子集异或和为0的子集大小之和。

分析

将问题转化为求每个元素的贡献次数之和。

先对n个数求线性基,设线性基大小为r,即插入线性基的数字个数为r,可以分别计算线性基内数的贡献和线性基外的数的贡献

  • 线性基外:共n-r个数,枚举每个数x,它可以和将线性基外剩余的n-r-1个数同时存在一个集合内,显然共有\(2^{n-r-1}\)个集合,所以x的贡献为\(2^{n-r-1}\)
  • 线性基内:枚举每个数x,将剩余的n-1个数再求一次线性基,设为B,分两种情况:
    • x不能被B异或出。那么显然x不能在任意一个集合中出现,x的贡献为0。
    • x可以被B异或出。此时B的大小必定也为r,因为B已经能表示所有n个数了。那么在除去x和B的情况下,x可以和剩余的n-r-1个数同时存在一个集合内,x的贡献为\(2^{n-r-1}\)

Code

#include<bits/stdc++.h>
#define fi first
#define se second
#define pb push_back
#define ll long long
using namespace std;
const int inf=1e9;
const int mod=1e9+7;
const int maxn=1e5+10;
int n;
ll a[maxn];
struct node{
    ll p[65];
    int cnt;
    void clear(){
        cnt=0;
        memset(p,0,sizeof(p));
    }
    bool insert(ll x){
        for(int i=60;i>=0;i--){
            if(x&(1ll<<i)){
                if(!p[i]){
                    p[i]=x;cnt++;
                    return 1;
                }
                x^=p[i];
            }
        }
        return 0;
    }
};
node x,y,z;
ll ksm(ll a,ll b){
    ll ret=1;
    while(b){
        if(b&1) ret=ret*a%mod;
        b>>=1;
        a=a*a%mod;
    }
    return ret;
}
int main(){
    //ios::sync_with_stdio(false);
    //freopen("in","r",stdin);
    while(~scanf("%d",&n)){
        x.clear();y.clear();z.clear();
        for(int i=1;i<=n;i++){
            scanf("%lld",&a[i]);
        }
        vector<ll>q,res;
        for(int i=1;i<=n;i++){
            if(x.insert(a[i])){
                q.pb(a[i]);
            }else res.pb(a[i]);
        }
        if(n==x.cnt){
            puts("0");
            continue;
        }
        ll ans=1ll*(n-x.cnt)*ksm(2,n-x.cnt-1)%mod;
        for(ll val:res){
            y.insert(val);
        }
        for(ll i:q){
            z=y;
            for(ll j:q){
                if(i==j) continue;
                z.insert(j);
            }
            if(!z.insert(i)) ans=(ans+ksm(2,n-x.cnt-1))%mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/xyq0220/p/11366669.html