[POJ 2773]Happy 2006(容斥原理+二分查找)

本文详细介绍了如何通过筛素数、分解质因数和应用容斥原理来解决寻找与给定整数互质的第K个数的问题。包括使用二分搜索优化算法效率,通过分解质因数和容斥原理计算特定范围内的互质数数量,最后通过二分查找确定目标数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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


先筛素数打表,再二分猜答案,每次二分时求得区间[1,mid]有多少个与m互质的数,多了缩小范围,少了扩大范围,相等就继续二分,使得最终mid等于最后一个与m互质的数。在求一个区间中有多少与m互质的数时,先对m分解质因数,然后根据容斥原理,DFS搜索,先减再加再减再加。。。这个过程略复杂,我还没完全搞明白,就不详述了。

#include <iostream>
#include <cmath>
#include <algorithm>
#include <cstdio>

#define ll long long int
#define MAXN 1010

using namespace std;

bool isPrime[MAXN];
int Prime[MAXN],PriNum[MAXN],cnt,tot;
int m,k;
ll res;

ll gcd(ll a,ll b)
{
    if(a%b==0) return b;
    return gcd(b,a%b);
}

void GetPrime() //筛素数,打表获得1000以内的质数
{
    cnt=0;
    for(int i=1;i<MAXN;i++) isPrime[i]=true;
    for(int i=2;i<MAXN;i++)
        if(isPrime[i])
        {
            Prime[++cnt]=i;
            for(int j=i*2;j<MAXN;j+=i) //i的倍数都不是素数
                isPrime[j]=false;
        }
}

void Cal(int cur) //对cur分解质因数,最终结果保存在PriNum数组中,无重复质因数
{
    tot=0;
    for(int i=1;Prime[i]*Prime[i]<=cur;i++)
    {
        if(!(cur%Prime[i])) //发现新的质因数
        {
            PriNum[++tot]=Prime[i]; //记录质因数
            while(!(cur%Prime[i])) cur/=Prime[i];
        }
    }
    if(cur!=1) PriNum[++tot]=cur; //剩余的不是1,则新增一个质因数
}

void dfs(ll hav,int cur,int num,ll va) //容斥原理求[1,va)中与m互质的数的个数,num为偶数则加,奇数则减
{
    if(cur>tot||(hav>va)) return;
    for(int i=cur;i<=tot;i++)
    {
        ll temp=hav*PriNum[i];
        if(num&1) //这一步是要减去的
            res-=va/temp;
        else
            res+=va/temp;
        dfs(temp,i+1,num+1,va);
    }
}

void solve(ll cur) //求出1~cur之间与m互质的数的个数
{
    res=cur;
    for(int i=1;i<=tot;i++)
    {
        res-=cur/PriNum[i];
        dfs(PriNum[i],i+1,2,cur);
    }
}

int main()
{
    GetPrime(); //先打表求出需要的素数
    while(~scanf("%d%d",&m,&k))
    {
        ll ans,L,R,M;
        L=1,R=(1LL<<62LL); //初始化二分边界为[1,+∞)
        Cal(m);
        while(L<=R) //二分猜答案
        {
            M=(L+R)>>1;
            solve(M); //获得[1,M]之间与m互质的数的个数
            ll temp=res;
            if(temp<k) //数的个数不够,需要放大范围
                L=M+1;
            else if(temp==k) //数的个数刚刚好,则二分查找最后一个互质的数
            {
                if(gcd(M,m)==1) //最后一个数刚好与m互质,找到了答案
                {
                    ans=M;
                    break;
                }
                R=M-1;
            }
            else R=M-1; //否则,数太多了,需要缩小范围
        }
        printf("%lld\n",ans);
    }
    return 0;
}
然后叉姐提供给我一个简洁明了的公式,此题的答案就是sum_{d | n} miu(d) [m / d],仔细琢磨下发现这个公式实在精辟,神得不能再神!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值