[清华集训D1T1][Bzoj 3811][思维+线性基]玛里苟斯

想像一下,我们将异或值x拆成若干个2的次方加在一起,那么k次方的意义便是:
从这些2次方中挑出k个(有序)2次方,将它们乘起来的和。
具体一点,bi是x的二进制表达从右往左的第i+1位,于是x可以拆成

而k次方要求我们求从中挑出k个项(有序,可以重复选同一项)的和
我们令ai,j表示ai二进制上第j位的数
第一步
我们先证明这些异或值的期望的小数部分必然为0.5或没有
我们从中挑出k个,记为



才会有贡献
考虑有多少个子集满足条件
我们相当于要求解x1~xn,满足




可以轻松地判断有没有解(即不存在一行的a全是0)
然后,有解的话,
这会是一个高斯消元法解异或方程,解的个数为2^(n-非零行的个数)
注意这里由于可能会有li=lj,但i!=j,所以这里的解方程需要去掉相同的若干行
设非零行有y行
那么贡献为

注意到,y意味着至多有y个不同的l,所以这些不同的l中至少有y-1是正的
即整体l的和(算重)是大于等于y-1的
故小数位要么为0,要么为0.5
第二步
通过答案小于2^64,我们得出一个很重要的性质:
k=1:ai<2^64
k=2:ai<2^32
k=3:ai<2^21
k=4:ai<2^16
k=5:ai<2^13
对于k=1和2,按照我们刚才的统计方式即可。
具体一点,就是枚举l,算出对应的贡献
对于k>2,我们有一个很明显的性质,
对于集合A和ai,若集合A的异或和为ai且ai不属于A,
去掉ai后答案不变。
我们可以用线性基实现去掉ai
显然,剩下的数最多21个
爆搜即可
最后统计答案时有些细节注意一下即可。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
typedef unsigned long long ull;
int n,k;
#define Maxn 1000010
ull a[Maxn];
ull num[70];
ull b[70];
int cnt=0;
ull Ans1=0,Ans2=0;
ull All=0;
void dfs(int u,ull ans){
    if(u==cnt+1){
        ull t1=0,t2=1;
        for(int i=1;i<=k;++i){
            t2*=ans;
            t1*=ans;t1+=(t2>>cnt);
            t2&=All;
        }
        Ans1+=t1;Ans2+=t2;
        if(Ans2>All)Ans1++,Ans2&=All;
        return;
    }
    dfs(u+1,ans);
    dfs(u+1,ans^b[u]);
}
int main(){
    scanf("%d%d",&n,&k);
    for(register int i=1;i<=n;++i)scanf("%llu",&a[i]);
    if(k==1){
        for(register int i=1;i<=n;++i)All|=a[i];
        ull Ans=0;
        for(int i=0;i<=63;++i)
            if(All&(1ull<<i))Ans=(Ans+(1ull<<i));
        if(Ans&1)printf("%llu.5\n",Ans/2ull);
        else printf("%llu\n",Ans/2ull);
        return 0;
    }
    if(k==2){
        ull Ans=0;
        for(register int i=1;i<=n;++i)All|=a[i];
        for(register int i=0;i<=31;++i)
            for(register int j=i;j<=31;++j)
                if((All&(1ull<<i))&&(All&(1ull<<j)))Ans=(Ans+(1ull<<(i+j)));
        if(Ans&1)printf("%llu.5\n",Ans/2ull);
        else printf("%llu\n",Ans/2ull);
        return 0;
    }
    for(register int i=1;i<=n;++i){
        ull pre=a[i];
        for(register int j=20;j>=0;--j)
            if(a[i]&(1ull<<j)){
                if(!num[j]){num[j]=a[i];break;}
                a[i]^=num[j];
            }
        if(a[i])b[++cnt]=pre;
    }
    All=(1<<cnt)-1;
    dfs(1,0);
    if(Ans2)printf("%llu.5\n",Ans1);
    else printf("%llu\n",Ans1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/ezoilearner/article/details/82918838