bzoj 4454: C Language Practice O(n)-O(1) gcd

O(n)-O(1) gcd
数n的质因数分解最多只有一个数大于sqrt(n),且那个数一定是素数
一个数一定可以分解成三个数相乘,每个数<=sqrt(n)或是素数。
于是,我们可以通过O(n)的预处理,以O(1)的效率回答两个数的Gcd
计算0~sqrt(n)之间的数两两的gcd,1到n的三个数相乘的分解。
然后计算两个数的gcd
枚举一个数的这三个因数,设枚举到的数是x
如果x<=sqrt(n)那么就可以得到因数gcd(x,y%x)
否则,x是个质数,只要看另一个数是否是x的倍数。
过程大体如下:

int Gcd(int x,int y)
{
int ans=1;
for (int i=0;i<3;i++)
if (a[x][i]>1)
{
int d;
if (a[x][i]<=k) d=gcd[a[x][i]][y%a[x][i]];
else if (y%a[x][i]==0) d=a[x][i];
else d=1;
ans*=d; y/=d;
}
return ans;
}
然而这道题卡常数+卡内存+卡卡卡卡。。。
压内存:那个记录素数的数据可以开小了一点。
压常数:如果两个数都<=sqrt(n) 那么直接返回答案。

#include
 
#define ll long long
#define inf 1e9
#define eps 1e-8
#define md
#define N 1000010
using namespace std;
 
const int k=1000,mxn=1000000;
int gcd[1005][1005],a[N][3],p[N],ss[100010];
int f[2010],g[2010];
bool mark[N];
 
void ycl()
{
for (int i=0;i<=k;i++)
for (int j=0;j<=i;j++)
if (!i||!j) gcd[i][j]=gcd[j][i]=i+j;
else gcd[i][j]=gcd[j][i]=gcd[j][i%j];
int w=0; p[1]=1;
for (int i=2;i<=mxn;i++)
{
if (!mark[i]) ss[++w]=i,p[i]=i;
for (int j=1;j<=w&&ss[j]*i<=mxn;j++)
{
mark[i*ss[j]]=1; p[i*ss[j]]=ss[j];
if (i%ss[j]==0) break;
}
}
 
a[1][0]=a[1][1]=a[1][2]=1;
for (int i=2;i<=mxn;i++)
{
for (int j=0;j<3;j++) a[i][j]=a[i/p[i]][j];
if (a[i][0]*p[i]<=k) a[i][0]*=p[i];
else if (a[i][1]*p[i]<=k) a[i][1]*=p[i];
else a[i][2]*=p[i];
}
}
 
unsigned int Gcd(int x,int y)
{
if (x<=k&&y<=k) return gcd[x][y];
if (!x||!y) return x+y;
int ans=1;
for (int i=0;i<3;i++)
if (a[x][i]>1)
{
int d;
if (a[x][i]<=k) d=gcd[a[x][i]][y%a[x][i]];
else if (y%a[x][i]==0) d=a[x][i];
else d=1;
ans*=d; y/=d;
}
return ans;
}
int main()
{
ycl();
int tt;
scanf("%d",&tt);
while (tt--)
{
int n,m;
scanf("%d%d",&n,&m);
for (int i=0;i<n;i++) scanf("%d",&f[i]);
for (int j=0;j<m;j++) scanf("%d",&g[j]);
unsigned int ans=0;
for (unsigned int i=0;i<n;i++)
for (unsigned int j=0;j<m;j++)
ans+=Gcd(f[i],g[j])^i^j;
printf("%u\n",ans);
}
return 0;
}

你可能感兴趣的:(bzoj 4454: C Language Practice O(n)-O(1) gcd)