2020牛客暑期多校第二场E-Exclusive OR(FWT)

博客园食用链接:https://www.cnblogs.com/lonely-wind-/p/13474701.html
题目大意:给你n个数, a i ( 0 ≤ a i < 2 18 ) a_i(0\leq a_i<2^{18}) ai(0ai<218),你现在需要选出其中 1 , 2 , 3 , . . . . n 1,2,3,....n 1,2,3,....n个数,使得其 a 1 ⨁ a 2 ⨁ . . . ⨁ a i a_1\bigoplus a_2\bigoplus ...\bigoplus a_i a1a2...ai的值最大。 1 ≤ n ≤ 2 × 1 0 5 1\leq n\leq 2\times 10^5 1n2×105

输入
4
1 4 5 7

输出
7 6 7 7

说明
i = 1 : 7 = 7 {i = 1 : 7 = 7} i=1:7=7
i = 2 : 7 ⊕ 1 = 6 i = 2 : 7\oplus 1 = 6 i=2:71=6
i = 3 : 7 ⊕ 1 ⊕ 1 = 7 i = 3 : 7\oplus 1 \oplus 1 = 7 i=3:711=7
i = 4 : 7 ⊕ 1 ⊕ 4 ⊕ 5 = 7 i = 4 : 7\oplus 1 \oplus 4 \oplus 5 = 7 i=4:7145=7

对于只取一个数的情况我们很容易知道它是个什么状态,那么取两个数的情况一定是再取一个的基础上再取一个的,取三个数的情况是在取两个数的情况下再取一个的…依此类推,那么我们就大概可以得到第 i i i个状态是由第 i − 1 , 1 i-1,1 i1,1两个转移过来的。

那么我们用 f 1 [ x ] f_1[x] f1[x]表示只取一个数的时候 x x x是否存在,那么我们对于第 i i i个数判断 x x x是否存在的时候就是去看看是否有 f i − 1 , f 1 f_{i-1},f_1 fi1,f1的某两位数相异或等于 x x x。那么很明显,我们不能直接枚举,那么做个式子过来就是 f i [ x ] = ∑ y ⊗ z = x f i − 1 [ y ] f 1 [ z ] f_i[x]=\sum_{y\otimes z=x}f_{i-1}[y]f_1[z] fi[x]=yz=xfi1[y]f1[z]那么我们就可以发现这是个异或卷积。。。我们每次取的时候卷一下就好了,但取的次数有 n n n次,那么如果每一次都卷的话,复杂度就是 n w l o g ( w ) nwlog(w) nwlog(w) w ≤ 2 18 w\leq2^{18} w218。。然后就会发现直接爆炸!

所以我们必须要优化时间,实际上对于 i > 19 i>19 i>19的情况我们可以直接计算,并不需要进行卷积,首先对于 ∀ i ∈ N + , a n s ≥ a n s i − 2 \forall i\in N^+,ans_{}\geq ans_{i-2} iN+,ansansi2,因为只需要再 a i a_i ai的基础上任意取一个数两次就可以满足了,如果 i > 19 i>19 i>19后有 a n s i > a n s i − 2 ans_{i}>ans_{i-2} ansi>ansi2那么也就是说必须要 i − 1 i-1 i1个数才能满秩,而这里的 i − 1 i-1 i1至少为19,而题目的数据只有 18 18 18,所以矛盾了,即 a n s i = a n s i − 2 ans_{i}=ans_{i-2} ansi=ansi2

那么我们只需要对前 19 19 19个数进行卷积就OK了。

以下是AC代码:

#include <bits/stdc++.h>
using namespace std;

const int mac=3e5+10;
const int mod=998244353;

namespace FWT{
    const int MOD=998244353;
    inline int FAdd(const int &a,const int &b){return a+b>=MOD? a+b-MOD:a+b;}
    inline int FSub(const int &a,const int &b){return a-b<0? a-b+MOD:a-b;}
    inline int FMul(const int &a,const int &b){return 1ll*a*b%MOD;}
    inline int FPow(int a,int b){int ret=1;while(b){if(b&1)ret=FMul(ret,a);a=FMul(a,a);b>>=1;}return ret;}
    const int INV2=FPow(2,MOD-2);
    void FWTOR(int *ary,const int &len,const int &typ){
        for(int L=2,T=1;L<=len;L<<=1,T<<=1)
            for(int i=0;i<len;i+=L)
                for(int j=i;j<i+T;j++)
                    if(typ==1) ary[j+T]=FAdd(ary[j+T],ary[j]);
                    else ary[j+T]=FSub(ary[j+T],ary[j]);
    }
    void FWTAND(int *ary,const int &len,const int &typ){
        for(int L=2,T=1;L<=len;L<<=1,T<<=1)
            for(int i=0;i<len;i+=L)
                for(int j=i;j<i+T;j++)
                    if(typ==1) ary[j]=FAdd(ary[j],ary[j+T]);
                    else ary[j]=FSub(ary[j],ary[j+T]);
    }
    void FWTXOR(int *ary,const int &len,const int &typ){
        for(int L=2,T=1;L<=len;L<<=1,T<<=1)
            for(int i=0;i<len;i+=L)
                for(int j=i;j<i+T;j++){
                    int Aj=ary[j];
                    ary[j]=FAdd(Aj,ary[j+T]);
                    ary[j+T]=FSub(Aj,ary[j+T]);
                    if(typ==-1)
                        ary[j]=FMul(ary[j],INV2),ary[j+T]=FMul(ary[j+T],INV2);
                }
    }
}

int f[22][mac],ans[mac];
int fcp[mac],f1[mac];

int main(int argc, char const *argv[])
{
    int n,mx,nw=0;
    scanf ("%d",&n);
    for (int i=1; i<=n; i++){
        int x;
        scanf ("%d",&x);
        ans[1]=max(ans[1],x); fcp[x]=1;
        f1[x]=1; nw=max(x,nw);
    }
    mx=1; 
    while (mx<=nw) mx<<=1;
    int typefront=1,typeback=-1;
    for (int i=2; i<=19; i++){
        for (int j=0; j<=mx; j++) f[1][j]=fcp[j];
        FWT::FWTXOR(f[1],mx,typefront);
        if (i!=2) FWT::FWTXOR(f[i-1],mx,typefront);
        else FWT::FWTXOR(f1,mx,typefront);
        if (i!=2) for (int j=0; j<=mx; j++) f[i][j]=1LL*f[1][j]*f[i-1][j]%mod;
        else for (int j=0; j<=mx; j++) f[i][j]=1LL*f[1][j]*f1[j]%mod;
        FWT::FWTXOR(f[i],mx,typeback);
        for (int j=0; j<=mx; j++)
            if (f[i][j]) ans[i]=j;
    }
    for (int i=20; i<=n; i++) ans[i]=ans[i-2];
    for (int i=1; i<=n; i++) printf("%d ",ans[i]); printf("\n");
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值