做完这道题之后,自我感觉收获还是挺大的。
题意:给出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;
}