poj 2733 欧拉函数 -容斥定理求互质个数

题解思路来自:https://blog.csdn.net/sdz20172133/article/details/81559687

Happy 2006

Happy 2006

Time Limit: 3000MS Memory Limit: 65536K
Total Submissions: 14060 Accepted: 4968

Description

Two positive integers are said to be relatively prime to each other if the Great Common Divisor (GCD) is 1. For instance, 1, 3, 5, 7, 9...are all relatively prime to 2006. 

Now your job is easy: for the given integer m, find the K-th element which is relatively prime to m when these elements are sorted in ascending order. 

Input

The input contains multiple test cases. For each test case, it contains two integers m (1 <= m <= 1000000), K (1 <= K <= 100000000).

Output

Output the K-th element in a single line.

Sample Input

2006 1
2006 2
2006 3

Sample Output

1
3
5

Source

POJ Monthly--2006.03.26,static

  
  

 

题目大意:给你一个数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;
}

 

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值