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

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

先筛素数打表,再二分猜答案,每次二分时求得区间[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],仔细琢磨下发现这个公式实在精辟,神得不能再神!


你可能感兴趣的:([POJ 2773]Happy 2006(容斥原理+二分查找))