题解思路来自:https://blog.csdn.net/sdz20172133/article/details/81559687
Happy 2006
| |||||||||
|
题目大意:给你一个数m,让你求与这个数互素的第k个数
方法一:(1767MS)
在1~m之间有phi[m](phi[m]是m的欧拉函数)个数与m互质,而在m+1~2*m之间也有phi[m]个数与m互质,(假设2与m互质,2+m肯定与m互质,所以与m互质的数挨个相加即可)且在n*m+1~(n+1)*m之间也有phi[m]个数与m互质,并且这phi[m]个数与在1~m之间的phi[m]个数是相互对应的。
所以我们先求出1~m之间的phi[m],然后直接跳到相应的区间,然后开始枚举,优化了时间。
THis is the code
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include <iomanip>
#include<list>
#include<map>
#include<queue>
#include<sstream>
#include<stack>
#include<string>
#include<set>
#include<vector>
using namespace std;
#define PI acos(-1.0)
#define EPS 1e-8
#define MOD 1e9+7
#define LL long long
#define ULL unsigned long long //1844674407370955161
#define INT_INF 0x7f7f7f7f //2139062143
#define LL_INF 0x7f7f7f7f7f7f7f7f //9187201950435737471
const int dr[]={0, 0, -1, 1, -1, -1, 1, 1};
const int dc[]={-1, 1, 0, 0, -1, 1, -1, 1};
// ios.sync_with_stdio(false);
// 那么cin, 就不能跟C的 scanf,sscanf, getchar, fgets之类的一起使用了。
const int N=1000000;
int phi[N+5];
bool v[N+5];
vector<int > prime;
void Getphi()
{
phi[1]=1; // 注意一定要有
prime.clear();
for(int i=2;i<=N;++i)
{
if(!v[i])
{
prime.push_back(i);
phi[i]=i-1;
}
for(int j=0;j<prime.size()&&i*prime[j]<=N;++j)
{
v[i*prime[j]]=true;
if(!(i%prime[j]))
{
phi[i*prime[j]]=phi[i]*prime[j];
break;
}
else
phi[i*prime[j]]=phi[i]*(prime[j]-1);
}
}
}
LL gcd(LL a,LL b)
{
return b? gcd(b,a%b):a;
}
int main()
{
Getphi();
LL m,k;
while(scanf("%lld%lld",&m,&k)!=EOF)
{
LL num=k/phi[m];
if(!(k%phi[m]))//区间是从零开始计数的,零表示最开始的那个
num--;
k=k-phi[m]*num; //表示正确区间内的第几个数
LL i=num*m+1; //区间的第一个数
LL ans;
for( i ; k!=0; ++i)
{
if(gcd(i,m)==1)
{
ans=i;
--k;
}
}
printf("%lld\n",ans);
}
return 0;
}
方法二:公式法(2829MS)
gcd(a+b*k, b) = gcd(b, a%b), gcd(a, b) = gcd(b, a%b), k为常数(不代表题目中的k)。
这表明了:对于与b互素的数,他们对b取模的余数会周期性出现。 那么我们就只需要计算出在b的范围内, 与b互素的数有哪些就可以了。
然后看第k个与b互素的数是在第几个周期的第几个就可以了。(注意:刚好在周期末时, 需要特判)
具体解决方案在代码
This is the code
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include <iomanip>
#include<list>
#include<map>
#include<queue>
#include<sstream>
#include<stack>
#include<string>
#include<set>
#include<vector>
using namespace std;
#define PI acos(-1.0)
#define EPS 1e-8
#define MOD 1e9+7
#define LL long long
#define ULL unsigned long long //1844674407370955161
#define INT_INF 0x7f7f7f7f //2139062143
#define LL_INF 0x7f7f7f7f7f7f7f7f //9187201950435737471
const int dr[]={0, 0, -1, 1, -1, -1, 1, 1};
const int dc[]={-1, 1, 0, 0, -1, 1, -1, 1};
// ios.sync_with_stdio(false);
// 那么cin, 就不能跟C的 scanf,sscanf, getchar, fgets之类的一起使用了。
const int N=1000000;
int phi[N+5];
LL gcd(LL a,LL b)
{
return b? gcd(b,a%b):a;
}
int main()
{
LL m,k;
while(scanf("%lld%lld",&m,&k)!=EOF)
{
int sum=0;
for(int i=1;i<=m;++i)
{
if(gcd(i,m)==1)
phi[++sum]=i;
}
if(k%sum)
printf("%lld\n",k/sum*m+phi[k%sum]);
else
printf("%lld\n",(k/sum-1)*m+phi[sum]);
}
return 0;
}
方法三:容斥定理 (思路来自:https://blog.csdn.net/sdz20172133/article/details/81565139)
根据唯一分解定理: 对一个数进行因式分解, 最终必定能分解为多个素数相乘的式子, 且这个式子是唯一的(1除外)。
我们假设1~mid就是满足有k个与m互素的数,然后我们保证mid最小就可以了。
二分的思想,从k到inf进行二分,二分出最小的mid,符合有k个与m互素的数的数。此时的 l 就是解
LL l=k,r=LL_INF,mid;
while(l<=r)
{
mid=(l+r)/2;
if(phi(mid)>=k)
r=mid-1;
else
l=mid+1;
}
就1到mid中有多少个与m互素的数需要用到 容斥原理(详解从里面找):
具体代码:
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include <iomanip>
#include<list>
#include<map>
#include<queue>
#include<sstream>
#include<stack>
#include<string>
#include<set>
#include<vector>
using namespace std;
#define PI acos(-1.0)
#define EPS 1e-8
#define MOD 1e9+7
#define LL long long
#define ULL unsigned long long //1844674407370955161
#define INT_INF 0x7f7f7f7f //2139062143
#define LL_INF 0x7f7f7f7f7f7f7f7f //9187201950435737471
const int dr[]={0, 0, -1, 1, -1, -1, 1, 1};
const int dc[]={-1, 1, 0, 0, -1, 1, -1, 1};
// ios.sync_with_stdio(false);
// 那么cin, 就不能跟C的 scanf,sscanf, getchar, fgets之类的一起使用了。
const int N=1e6;
LL fac[N+5];
LL m,k,sum=0;
void prime_fac() //获取质因子
{
sum=0;
LL temp=m;
for(LL i=2;i*i<=temp;++i) //官方版的唯一分解定理
if(temp%i==0)
{
fac[sum++]=i;
while(temp%i==0)
temp/=i;
}
if(temp>1)
fac[sum++]=temp;
}
LL phi(LL temp)//容斥原理的实现
{
if(m==1)
return temp;
if(temp==1)
return 1;//两个剪枝
LL ret=0;
//二进制枚举
for(int i=1;i<(1<<sum);++i) //从0~2^n-1个状态,遍历每一个集合
{
LL cnt=0,p=1;
for(int j=0;j<sum;++j) //遍历二进制的每一位
{
if(i&(1<<j))//判断二进制第j位是否存在, 注意这里用了按位且 &
{
p*=fac[j];
cnt++;
}
}
if(cnt&1) //cnt为奇数 ,容斥原理
ret+=temp/p;
else
ret-=temp/p;
}
return temp-ret;
}
int search_two()//二分搜索互素的个数,寻找第k个互素的数
{
LL l=k,r=LL_INF,mid;
while(l<=r)
{
mid=(l+r)/2;
if(phi(mid)>=k)
r=mid-1;
else
l=mid+1;
}
printf("%lld\n",l);
}
int main()
{
while(scanf("%lld%lld",&m,&k)!=EOF)
{
prime_fac();
search_two();
}
return 0;
}