UVA11327 Enumerating Rational Numbers 题解

Description

Luogu传送门

Solution

又是一道诈骗题。

观察题目给出的伪代码,不难发现,对于每一个 \(d\),合法的 \(n\) 的个数有 \(\varphi(d)\) 个。

但是 \(k\) 这么大,我们怎么求呢?

继续观察样例,可以发现,样例中给出了 \(k\) 取最大值时的答案。

我们惊讶的发现,分母最大竟然只有 200000 !

所以我们就可以用线性筛筛出 \(2\times10^5\) 以内的 \(\varphi\),并做个前缀和。

对于每一组询问 \(k\),先二分出来小于等于 \(k\) 的最大的数是多少,用 \(k\) 减去它。然后对于当前分母暴力枚举分子,判断二者是否互质即可。

注意:\(\gcd(0, a) = a\),所以有且仅有 \(\gcd(0, 1) = 1\),要特判一下。
另外这题的边界也有一点恶心,建议自己好好思考一下。

Code

关于代码里的各种玄学写法,烦请各位自行模拟一下QwQ

#include <bits/stdc++.h>
#define ll long long

using namespace std;

namespace IO{
    inline ll read(){
        ll x = 0;
        char ch = getchar();
        while(!isdigit(ch)) ch = getchar();
        while(isdigit(ch)) x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
        return x;
    }

    template <typename T> inline void write(T x){
        if(x > 9) write(x / 10);
        putchar(x % 10 + '0');
    }
}
using namespace IO;

const ll N = 2e5 + 10;
ll k, tmp, n, d;
ll phi[N], sum[N], p[N], tot;
bool vis[N];

inline void euler(){
    phi[1] = 1;
    for(ll i = 2; i < N; ++i){
        if(!vis[i]) p[++tot] = i, phi[i] = i - 1;
        for(ll j = 1; j <= tot && i * p[j] < N; ++j){
            vis[i * p[j]] = 1;
            if(i % p[j]) phi[i * p[j]] = phi[i] * (p[j] - 1);
            else{
                phi[i * p[j]] = phi[i] * p[j];
                break;
            }
        }
    }
    phi[1]++;
    for(ll i = 1; i < N; ++i) sum[i] = sum[i - 1] + phi[i];
}

inline ll gcd(ll a, ll b){
    return !b ? a : gcd(b, a % b);
}

signed main(){
    euler();
    while(scanf("%lld", &k) && k){
        if(k == 1) {puts("0/1"); continue;}
        if(k == 2) {puts("1/1"); continue;}
        d = upper_bound(sum, sum + N, k) - sum - 1;
        k -= sum[d++];
        if(!k) {printf("%lld/%lld\n", d - 2, d - 1); continue;}
        for(n = 1; k; ++n) k -= (gcd(n, d) == 1);
        printf("%lld/%lld\n", n - 1, d);
    }
    return 0;
}

\[\_EOF\_ \]

posted @ 2021-12-17 22:17  xixike  阅读(55)  评论(0编辑  收藏  举报