题目:http://poj.org/problem?id=2773 和 http://acm.nyist.net/JudgeOnline/problem.php?pid=762
题意:求与n的互质的第k个数(互素的数从1递增),第k个数可能比n大。
分析:这里介绍两种方法,但nyoj上面的数据比poj强,只有第二种在nyoj上面能AC。
方法一:与n互质的数的增量有成倍关系,比如在1~n内有4个数(p1,p2,p3和p4)与n互质,那么与n互质的数按从小到大的排列为p1,p2,p3,p4,n+p1,n+p2,n+p3,n+p4,2n+p1,2n+p2........这样就可以先把1~n内所有与n互质的数求出来(O(n)*O(gcd)),再根据规律求出第k个互质的数(O(1))。
代码:
#include <iostream>
#include <cstdio>
using namespace std;
typedef long long LL;
int gcd(int a,int b)
{
return b==0?a:gcd(b,a%b);
}
int pp[1000001];
int main()
{
int i,j,n,k,num;
int ans;
while(scanf("%d%d",&n,&k)!=EOF)
{
num=0;
for(i=1;i<=n;i++)
if(gcd(n,i)==1)
pp[++num]=i;
if(k%num==0)
ans=n*(k/num-1)+pp[num];
else
ans=k/num*n+pp[k%num];
printf("%d\n",ans);
}
return 0;
}
方法一的优化:方法一的时间消耗主要是求gcd(n,i),i:1~n。可以不用求gcd。可以用n的素数因子,筛选出1~n内与n互素的数,因为n的素数因子的倍数肯定不与n互质。(O(n))
优化后的代码:
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int maxn=1000001;
int prime[maxn],nprime;
bool isprime[maxn]; //用了两次,第一次筛选素数,第二次记录因子
void doprime()
{
nprime=0;
long long i,j;
for(i=1;i<maxn;i+=2)
isprime[i]=true;
isprime[1]=false;
isprime[2]=true;
for(i=2;i<maxn;i++)
if(isprime[i])
{
prime[nprime++]=i;
for(j=i*i;j<maxn;j+=i)
isprime[j]=false;
}
return ;
}
int euler(int n) //isprime[j]=0 表示j不与n互素
{
int ans=n,temp=n;
int i,j,k=sqrt(0.0+n)+1;
for(i=0;prime[i]<=k;i++)
{
if(n%prime[i]==0)
{
ans=ans-ans/prime[i];
n/=prime[i];
while(n%prime[i]==0)
n/=prime[i];
for(j=prime[i];j<=temp;j+=prime[i])//n的素因子的倍数不与n互质
isprime[j]=0;
if(n==1)
break;
}
}
if(n>1)
{
ans=ans-ans/n;
for(j=n;j<=temp;j+=n)
isprime[j]=0;
}
return ans;
}
int my_get(int c)
{
int i,cnt=0;
for(i=1;i<maxn;i++)
if(isprime[i])
{
cnt++;
if(cnt==c)
return i;
}
}
int main()
{
doprime();
int n,k,i,j,num;
while(scanf("%d%d",&n,&k)!=EOF)
{
if(n==1)
{
printf("%d\n",k);
continue;
}
for(i=0;i<=n;i++)
isprime[i]=1;
num=euler(n);
if(k%num==0)
printf("%d\n",(k/num-1)*n+my_get(num));
else
printf("%d\n",k/num*n+my_get(k%num));
}
return 0;
}
方法二:假设在1~w内有k个数与n互质,那么答案(第k个与n互质的数)就在1~w内。二分搜索,使w最小,那么w即为所求。剩下的问题就是怎么求1~w内有多少个数与n互质, http://blog.csdn.net/w20810/article/details/43882951
代码:
#include <iostream>
#include <cstdio>
#include <vector>
#include <cmath>
using namespace std;
typedef long long LL;
#define INF 1000000000
int solve(int n,int r) //区间[1,r]内与n互素的数的个数
{
vector<int> p;
for(int i=2; i*i<=n; i++)
{
if(n%i==0)
{
p.push_back(i);
while(n%i==0) n/=i;
}
}
if(n>1)
p.push_back(n);
LL ans=0;
for(int msk=1; msk<(1<<p.size()); msk++)
{
int multi=1,bits=0;
for(int i=0; i<p.size(); i++)
{
if(msk&(1<<i)) //判断第几个因子目前被用到
{
++bits;
multi*=p[i];
}
}
int cur=r/multi;
if(bits&1) ans+=cur;
else ans-=cur;
}
return r-ans;
}
int main()
{
int n,k,ans,p;
int down,mid,up;
while(scanf("%d%d",&n,&k)!=EOF)
{
if(n==1)
{
printf("%d\n",k);
continue;
}
down=1;
up=INF;
while(down<up)
{
mid=(up+down)>>1;
p=solve(n,mid);
if(p>=k)
{
if(p==k)
ans=mid;
up=mid;
}
else
down=mid+1;
}
printf("%d\n",ans);
}
return 0;
}