2588 GCD (数论题)

做完这道题之后,自我感觉收获还是挺大的。

题意:给出N和M(M<=N), 求同时满足 1<=X<=N 和 gcd(X,N)>=M 的 X的个数

第一次ac的时候,用了31ms,基本思路是这样:

从2到sqrt(n+0.5)暴力枚举n的所有因子,然后对所有的因子d,求出对应的(n/d)的欧拉函数值oula(n/d),即比(n/d)小且与(n/d)互质的正整数的个数,

最后把他们加起来就是答案了。

但是看到那些排在前面的大牛都是0ms过的,所以这题还有更好的方法。

在网上搜了很多题解,但是没有找到我想要的0ms代码,最后经过文文大神的指导,终于敲出0ms的代码了。

思路是这样:

先求出n的所有不同的质因数b[0],b[1],b[2]......b[len-2],b[len-1]以及对应的个数,count[0],count[1],count[2].......count[len-1],其中个数为len

对于每个b[i],dfs枚举个数0~count[i],同时可以记录被枚举到的数的积对应的欧拉函数值。

所以这里主要有两个优化,1、把求欧拉函数的时间降到0  2、不需要暴力枚举2~sqrt(n+0.5)了(这个优化是次要的,主要的优化是第一个)。

下面附上之前的代码和优化后的代码:

优化前代码31ms,这个比较容易理解

#include<stdio.h>
#include<math.h>
int a[5000];
bool flag[30000];
int oulas(int s)//求欧拉函数值
{
	int hh=(int)sqrt(s+0.5),ff=s,i;
	for(i=0;a[i]<=hh;i++)
	{
		if(s%a[i]==0)
		{
			ff=ff/a[i]*(a[i]-1);
			while(s%a[i]==0)
				s/=a[i];
			if(s==1)break;
		}
	}
	if(s>1)ff=ff/s*(s-1);
    return ff;
}
int main()
{
	int i,j,len=0,n,m;
	for(i=2;i<=35000;i++)//筛选2~35000内的素数,方便后面计算
		if(!flag[i])
		{
          a[len]=i;
          len++;
		  for(j=i*i;j<=35000;j+=i)
			 flag[j]=1;
		}
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		if(m==1)
		{
			printf("%d\n",n);
			continue;
		}
		int ans=1,mm=(int)sqrt(n+0.5);
                for(i=2;i<mm;i++)//暴力枚举n的因数
		{
			if(n%i==0)
			{
				if(i>=m)
				  ans+=oulas(n/i);
		        if(n/i>=m)
				  ans+=oulas(i);
			}
		}
		if(mm>=m&&mm*mm==n)ans+=oulas(mm);
		printf("%d\n",ans);
	}
	return 0;
}

优化后0ms代码

#include<stdio.h>
#include<math.h>
#include<string.h>
int a[5000],lena,m,n;
int b[5000],count[5000],lenb;//记录n的质因数以及每个质因数对应的个数、总个数
bool flag[40000];
int dfs(int num,int cur,int dig)//枚举第num个质因子个数,从0~count[num],dig表示已被枚举到的数的积,cur表示已被枚举到的数的积对应的欧拉函数值即oula(dig)
{
	if(num==lenb)
	{
		if(n/dig>=m)//所有的数都被枚举过,当X>=m时,返回欧拉函数值
			return cur;
		else
			return 0;
	}
	int i,ans=0;
	ans+=dfs(num+1,cur,dig);//选取0个b[num]
	for(i=0;i<count[num];i++)
	{
		dig*=b[num];
		if(i==0)//选取第一个b[num]时,特殊考虑
		{
		   cur*=(b[num]-1);
                   ans+=dfs(num+1,cur,dig);
		   continue;
		}
		cur*=b[num];//不是第一个b[num]直接乘就行
		ans+=dfs(num+1,cur,dig);
	}
    return ans;
}
void init(int s)//求出n的所有质因数以及每个质因数对应的个数,以及总个数
{
	lenb=0;
	memset(count,0,sizeof(count));
	for(int i=0;i<lena;i++)
	{
		if(s%a[i]==0)
		{
                        while(s%a[i]==0)
			{
				count[lenb]++;
		        s/=a[i];
			}
			b[lenb++]=a[i];
		}
		if(s==1)break;
	}
	if(s>1)
	{
		count[lenb]=1;
		b[lenb++]=s;
	}
}
int main()
{
	int i,j;
	lena=0;
	for(i=2;i<=33000;i++)
		if(!flag[i])
		{
		  a[lena++]=i;
		  for(j=i*i;j<=33000;j+=i)
			 flag[j]=1;
		}
	int t;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%d%d",&n,&m);
		if(m==1)
		{
			printf("%d\n",n);
			continue;
		}
		init(n);
		printf("%d\n",dfs(0,1,1));//从第0个质数开始枚举
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值