牛客多校 Playing games FWT

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33229466/article/details/81591294

题意

给出n个数a1,a2,...,an,问最多选出多少个数使得这些数的异或和为0。
n,ai5105

分析

震惊!老年退役选手居然开始写题解了。
这次回家本来也没打算写题的,但是在今早某人问了我这个题,想了快一个小时终于会了,然后就用睡觉的时间把这题过掉了。刚好现在闲得无聊就过来把题解写一下也顺便证明下自己还活着。
咳咳咳,那现在就要开始口胡了。
我们可以转化成选出最少的数使得他们的异或和等于整个序列的异或和,设为w,不难发现答案不超过log(ai),不然的话选出的数一定是一个线性相关组。
一开始的想法是枚举答案,那么问题就转化成了对某个k,求所有大小为k的子集能否异或出w。这个显然可以用FWT来做,复杂度是俩log,有点虚。
接着想到或许可以二分,但这个显然是不满足二分性的,怎么办呢?那么我们可以变成求大小至多为k的子集能否异或出w,怎么实现呢?只要让初始生成函数的零次项系数为1即可。
这样复杂度就变为了O(nlog(n)log(log(n))),跑的飞快。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

const int N=1048591;
const int MOD=10007;
const int ny2=(MOD+1)/2;

int n,a[N],po[23][N],bin[23];

void FWT(int *a,int l,int r)
{
    if (l==r) return;
    int len=(r-l+1)/2,mid=(l+r)/2;
    FWT(a,l,mid);FWT(a,mid+1,r);
    for (int i=0;i<len;i++)
    {
        int u=a[l+i],v=a[l+len+i];
        a[l+i]=u+v;a[l+i]-=a[l+i]>=MOD?MOD:0;
        a[l+len+i]=u+MOD-v;a[l+len+i]-=a[l+len+i]>=MOD?MOD:0;
    }
}

void DWT(int *a,int l,int r)
{
    if (l==r) return;
    int len=(r-l+1)/2,mid=(l+r)/2;
    DWT(a,l,mid);DWT(a,mid+1,r);
    for (int i=0;i<len;i++)
    {
        int u=a[l+i],v=a[l+len+i];
        a[l+i]=(u+v)*ny2%MOD;a[l+len+i]=(u+MOD-v)*ny2%MOD;
    }
}

int main()
{
    bin[0]=1;
    for (int i=1;i<=20;i++) bin[i]=bin[i-1]*2;
    scanf("%d",&n);int w=0;
    for (int i=1;i<=n;i++)
    {
        int x;scanf("%d",&x);
        a[x]=1;w^=x;
    }
    a[0]=1;
    if (!w) {printf("%d\n",n);return 0;}
    FWT(a,0,bin[20]-1);
    for (int i=0;i<bin[20];i++) po[0][i]=1;
    for (int j=1;j<=20;j++)
        for (int i=0;i<bin[20];i++) po[j][i]=po[j-1][i]*a[i]%MOD;
    int l=1,r=20;
    while (l<=r)
    {
        int mid=(l+r)/2;
        DWT(po[mid],0,bin[20]-1);
        if (po[mid][w]) r=mid-1;
        else l=mid+1;
    }
    if (r==20) puts("0");
    else printf("%d\n",n-(r+1));
    return 0;
}
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页