POJ 2773 - Happy 2006(暴力+思维 / 二分+容斥)

题目链接 https://vjudge.net/problem/POJ-2773

【题意】
给定两个整数m和k,找到第k个与m互素的数(互素的数从小到大排列),其中(1 <= m <= 1000000,1 <= k <= 100000000 )

【思路】
有两种思路,先说一种比较暴力的思路,比较好理解。有一个重要的结论要知道是gcd(a+b*t,b)=gcd(a,b),也就是说如果a+b*t与b互素,那么a与b也互素,反之也成立。现在要找的是与m互素的数,假设我们已经求出了[1,m-1]内与m互素的每一个数,假设第i个为a[i],那么根据gcd(a[i]+m*t,m)=gcd(a[i],m),t=1,2,3…所以我们就可以得到一些列的数a[i]+m*t,t=1,2,3…都是和m互素的,因此我们可以暴力求解出前面的每一个a[i],然后再增加若干倍的m就是结果了

#include<cstdio>
using namespace std;
typedef long long ll;

const int maxm = 1000005;

int gcd(int a, int b) {
    return 0 == b ? a : gcd(b, a%b);
}

int a[maxm];

int main() {
    int m, k;
    while (scanf("%d%d", &m, &k) == 2) {
        int cnt = 0;
        for (int i = 1; i <= m; ++i) {
            if (gcd(m, i) == 1) {
                a[cnt++] = i;
            }
        }
        ll ans = a[(k - 1) % cnt];//下标从0开始,k-1向前偏移一位
        ans += (ll)m*(ll)((k - 1) / cnt);
        printf("%lld\n", ans);
    }
    return 0;
}

另外一种复杂度较低的算法思路是利用二分法,二分求解[1,x]中所有与m互素的数的个数,如果答案刚好为k,那么x就是第k个与m互素的数了。关键在于如何求解[1,x]中所有与m互素的数的个数,首先要对m进行唯一分解,拆分出m的每个素因子,然后根据容斥原理,用二进制法枚举每种素因子选择或不选择的状态,计算相应结果累加求和,举个例子假如要算[1,30]中所有与30互素的数,我们知道30=2*3*5,所以[1,30]中2的倍数有30/2=15个,3的倍数有30/3=10个,5的倍数有30/5=6个,既是2也是3的倍数即6的倍数有30/6=5个,既是2又是5的倍数即10的倍数有30/10=3个,既是3又是5的倍数即15的倍数有30/15=2个,既是2又是3还是5的倍数的数有1个就是30,所以由容斥原理,[1,30]中,与m不互素(即和m中有相同的素因子,也就是满足2或3或5的倍数)的个数=(15+10+6)-(5+3+2)+1=21个,那么再用30-22=8就是[1,30]中与m互素的数的个数。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;

const ll inf = 1e18;

ll m, k;
ll fac[10050], num;//素因数,素因数的个数

void init() {//唯一分解定理
    num = 0;
    ll cpy = m;
    int n = (int)sqrt(m + 0.5);
    for (int i = 2; i <= n; ++i) {
        if (cpy % i == 0) {
            fac[num++] = i;
            while (cpy % i == 0) cpy /= i;
        }
    }
    if (cpy > 1) fac[num++] = cpy;
}

ll check(ll x) {//返回[1,x]中与m互素的数的个数

    if (x == 1) return 1;
    if (m == 1) return x;

    ll ans = 0;//不互素的个数
    for (ll s = 1; s < (1 << num); ++s) {//二进制法枚举所有素因子的选择情况(注意从1开始枚举)
        int cnt = 0;
        ll f = 1;
        for (int j = 0; j < num; ++j) {
            if (s & (1 << j)) {
                ++cnt;
                f *= fac[j];
            }
        }
        //容斥原理统计,奇数加,偶数减
        if (cnt & 1) ans += x / f;
        else ans -= x / f;
    }
    return x - ans;
}

int main() {
    while (scanf("%lld%lld", &m, &k) == 2) {
        init();
        ll le = 0, ri = inf, mid;
        while (le + 1 < ri) {
            mid = (le + ri) >> 1;
            if (check(mid) < k) le = mid;
            else ri = mid;
        }
        printf("%lld\n", ri);
    }
    return 0;
}
posted @ 2018-02-25 18:33  不想吃WA的咸鱼  阅读(125)  评论(0编辑  收藏  举报