牛客练习赛 41 简单数学题(数论 + 状态压缩 + FWT)

 

 

非常有意思的题,我解题的过程也是非常的精彩。纪念一下人生第一道FWT。

首先是第一个f(t)函数,根据它的定义,首先是x必须是t的因子,然后要求莫比乌斯函数不为0,而且要满足这个条件的最大值。根据这几个条件,显然x就是t分解质因子后,所有质因子的乘积。然后g(x)函数,就是看每个因子的指数,如果是奇数那么这个质因子还在,否则相当于没有这个质因子。最后就是F(a,b,c),它等于g(a*b*c)。转换一下,相当于对a、b和c所有质因子的并集的函数。我们考虑每一个质因子pi,它的相乘之后的指数为di=ai+bi+ci。可以看到,di的取值只有可能是0、1、2和3,且ai、bi和ci是0或者1。当di为偶数时相当于质因子最后的指数为0,为奇数的时候相当于指数为1。如果你观察仔细的话,我们可以发现这样一个关系:

                                                                 \large B_i=a_i \oplus b_i \oplus c_i

然后,题目保证了a、b和c的最大质因子都不会超过71,而71以内恰好只有20个质数。20个质数加上这个异或,很好的提示了我们需要用到状态压缩,也即用位运算来快速计算F(a,b,c)的数值。我们用一个20位的二进制,表示一个数字是否有对应序号的质数作为其质因子。然后异或就可以得到最后答案的所有质因子,可以反推答案。但是,我们最后要求的是对于数列A、B和C中,所有的a、b和c求出所有F(a,b,c)的数值与其出现次数的乘积和。这里三个数列的长度都是可以到1e5的级别。如果暴力求所有的数值,即使用上位运算可以O(1)计算一个函数值,总的复杂度还是会到O(N^3)。

由于我们是异或操作,而且位数只有20,所以我们不管怎么异或我们的结果不会超过2^20。所以我们大可以用一个多项式来表示这2^20个数字每个数字出现的次数。这样我们就可以联想到FFT,FFT对应两个多项式相乘,得到结果是两个多项式任取两项的组合的结果加到项数的和里面。但是这里我们要求的是,结果应该加到二者的异或结果里面。误打误撞,突然发现这个恰好变成了FWT的模板题了。

简单说一下FWT。

对于普通的FFT,我们是计算:\large C_k=\sum_{i+j=k}A_i*B_j

但是对于FWT,我们是计算:

                                                         \large C_k=\sum_{i|j=k}A_i*B_j

                                                         \large C_k=\sum_{i\&j=k}A_i*B_j

                                                         \large C_k=\sum_{i\oplus j=k}A_i*B_j

功能大概是这样的,至于原理就不在这里说了,直接上模板。

具体来说,对于输入的每个数列中的每个数字,直接进行质因数分解,然后根据出现的质因子构造20位的二进制数字,把对应数列的数字出现次数加一。处理完三个数列之后,就会有三个多项式,把三个多项式用FWT卷起来。最后用每一项的出现次数乘以每一项二进制数对应的数值求和即可。具体见代码:

#include <bits/stdc++.h>
#define LL long long
#define sc(x) scanf("%d",&x)
#define scc(x,y) scanf("%d%d",&x,&y)
#define sccc(x,y,z) scanf("%d%d%d",&x&y,&z)
using namespace std;

const int M = 1<<20;
const int N = 1e5 + 10;
const int mod = 1e9 + 7;
const int inv2 = 5e8 + 4;
const int p[20]={2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71};

int A[M<<1],B[M<<1],C[M<<1],n;

void FWT(int *a,int opt)
{
    for(int i=1;i<M;i<<=1)
        for(int p=i<<1,j=0;j<M;j+=p)
            for(int k=0;k<i;++k)
            {
                int X=a[j+k],Y=a[i+j+k];
                a[j+k]=(X+Y)%mod;a[i+j+k]=(X+mod-Y)%mod;
                if(opt==-1)a[j+k]=1ll*a[j+k]*inv2%mod,a[i+j+k]=1ll*a[i+j+k]*inv2%mod;
            }
}

inline LL cal(int x)
{
    LL res=1;
    for(int i=0;i<20;i++)
        if (x&(1<<i)) res=res*p[i]%mod;
    return res;
}

int main()
{
    sc(n);
    for(int i=1;i<=n;i++)
    {
        LL x; int y=0;
        scanf("%lld",&x);
        for(int j=0;j<20&&x>1;j++)
        {
            if (x%p[j]) continue;
            y|=1<<j; while(x%p[j]==0) x/=p[j];
        }
        A[y]++;
    }
    for(int i=1;i<=n;i++)
    {
        LL x; int y=0;
        scanf("%lld",&x);
        for(int j=0;j<20&&x>1;j++)
        {
            if (x%p[j]) continue;
            y|=1<<j; while(x%p[j]==0) x/=p[j];
        }
        B[y]++;
    }
    for(int i=1;i<=n;i++)
    {
        LL x; int y=0;
        scanf("%lld",&x);
        for(int j=0;j<20&&x>1;j++)
        {
            if (x%p[j]) continue;
            y|=1<<j; while(x%p[j]==0) x/=p[j];
        }
        C[y]++;
    }
    FWT(A,1);FWT(B,1);FWT(C,1);
    for(int i=0;i<M;i++)
        A[i]=(LL)A[i]*B[i]%mod*C[i]%mod;
    FWT(A,-1); int ans=0;
    for(int i=0;i<M;i++)
    {
        ans=(ans+(LL)cal(i)*A[i]%mod)%mod;
    }

    printf("%d\n",ans);
    return 0;
}

 

 

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值