Processing math: 100%

对于 gcd的询问,设值域为 V,询问次数为 Q, 有一种奇奇怪怪的时空复杂度都是 O(V+Q),即 O(V)O(1)的做法。

题面

解题思路:

把值域内的数 x都分解成 3个都不大于 x的数相乘 (允许出现大于 x的质数),步骤如下:

  • 处理完所有小于 x的数
  • 找到 x最小的质因子 p
  • xp分解的 3个数赋值入 x中,并把最小的数 a乘上 p

我们只需要证明这个最小的数 a×pxa×p是质数,因为其它 2 个数不是大质数就是小于 xp, 肯定符合条件。
a=1时,因为 px的最小质因子,所以 pa, 又因为 a3xp
所以 p3xp
p 非负,所以把根号去掉变成
p3xp
p4x
另外,p×3xp明显是递增的(你也可以在这里直观感受一下
当 p 取最大值 4x时,a×p4x×3x4x=3x×4x2=3x32=x
特殊的,当 a=1时,p显然是质数,即使大于 x满足我们要求的性质

同时,上面的操作明显可以用线性筛完成

处理完之后,所有数基本都在 V以内了,那么我们就可以打个 V×Vgcd的表,每次求 gcd是可以 O(1)的,gcd[i][j]=gcd[j][i]=gcd[i  mod  j][j]。根据辗转相除法,这显然正确。查询 2 个大数 x,ygcd时,找出 a 的分解的 3个数 num1,num2,num3,对于每个 num,如果是质数直接判断能不能整除 b,否则求 gcd[numi][b  mod  numi],然后让 b除以这个数就行了。

代码

#include<bits/stdc++.h>
using namespace std;
#define rint register int
#define MN 5005
#define Maxn 1000005
#define Mod 998244353
#define BK 1005
#define LL long long
int n,a[MN],b[MN],Max,num[Maxn][3],GCD[BK][BK],table,vis[Maxn],p[Maxn],cnt;
inline int gcd(int x,int y){
    int res=1;
    for(int i=0;i<3;++i)
        if(num[x][i]>table){if(y%num[x][i]==0)y/=num[x][i],res*=num[x][i];}//大于表的范围,肯定是质数
            else {int tmp=GCD[num[x][i]][y%num[x][i]];res*=tmp;y/=tmp;}//y 除,答案乘
    return res;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i)scanf("%d",&a[i]),Max=max(Max,a[i]);//找出值域
    for(int i=1;i<=n;++i)scanf("%d",&b[i]),Max=max(Max,b[i]);
    table=sqrt(Max);
    num[1][0]=num[1][1]=num[1][2]=1;
    for(int i=2;i<=Max;++i){
        if(!vis[i])p[++cnt]=i,num[i][0]=num[i][1]=1,num[i][2]=i;
        for(int j=1,tmp;(tmp=p[j]*i)<=Max;++j){
            vis[tmp]=1;
            num[tmp][0]=num[i][0]*p[j];
            num[tmp][1]=num[i][1];
            num[tmp][2]=num[i][2];
            if(num[tmp][0]>num[tmp][1]){
                swap(num[tmp][0],num[tmp][1]);
                if(num[tmp][1]>num[tmp][2])
                    swap(num[tmp][1],num[tmp][2]);
            }
            if(i%p[j]==0)break;//线性筛
        }
    }
    for(int i=1;i<=table;++i)GCD[i][i]=GCD[i][0]=GCD[0][i]=i;
    for(int i=1;i<=table;++i)
        for(int j=1;j<i;++j)
            GCD[i][j]=GCD[j][i]=GCD[i%j][j];//和辗转相除差不多,O(1)
    for(int i=1;i<=n;++i){
        LL now=1,ans=0,f=1;
        for(int j=1;j<=n;++j){
            f=f*i%Mod;
            ans=(ans+(LL)f*gcd(a[i],b[j]))%Mod;
        }
        printf("%lld\n",ans);
    }
    return 0;
} 
C++
分类: 文章

1 条评论

skydogli · 2019年10月7日 10:00 下午

其实现在觉得这玩意好鸡肋啊,带个巨大常数,而在批量用 gcd 的时候复杂度大多数不是 log 的,所以朴素 gcd 爆踩这个算法。

发表回复

Avatar placeholder

您的邮箱地址不会被公开。 必填项已用 * 标注