UVa11327 - Enumerating Rational Numbers 题解

题目大意

我们知道每一个有理数都可以表示为 \(\frac{p}{q}\) 的形式,其中 \(\gcd(p,q)=1\)。现在我们枚举所有 \(<1\) 的有理数,很显然我们可以用下面这段伪代码:

for d = 1 to infinity do
    for n = 0 to d do
        if gcd(n,d) = 1 then print n / d

根据这样的代码可以构造出一个包含所有 \(<1\) 的有理数序列。现在有一个数 \(k\),请你求出这个序列中第 \(k\) 个数。

多组数据,每行一个正整数 \(k\),直到读入 \(k=0\) 为止。\(k\leq 12158598919\)

分析

伪代码表示的意思就是这个序列排序方式的第一关键字是分母,第二关键字是分子。而由于分数小于 \(1\),所以分子显然比分母小。

考虑以数 \(t\) 为分母的分数,那么这样的在序列中的数一共有 \(\sum\limits_{i=1}^n[\gcd(i,n)=1]\) 个。而这正好是欧拉函数 \(\varphi(n)\) 的定义式。

所以我们先筛出欧拉函数,记录欧拉函数的前缀和,然后看一看这个 \(k\) 在哪两个前缀和之间。这一过程可以用二分实现,也就是二分出了分母。接下来我们拿 \(k\) 减去我们二分出的分母 \(q\) 的欧拉函数,也就是 \(x=k-\varphi(q)\)。这表示:所求的数是以 \(q\) 为分母的第 \(x\) 个有理数,这一部分可以使用暴力遍历解决。那么这样的运算次数应该是 \(2\times 10^5\ +\ \log(2\times 10^5)+\ 2\times 10^5 \times \log(2\times 10^5)\),是一个可以接受的复杂度。这里将 \(k\leq 12158598919\) 使用分母 \(q\leq2\times 10^5\) 代替了。也可以理解为值域。

代码

#include<bits/stdc++.h>
#define HohleFeuerwerke using namespace std
#pragma GCC optimize(3,"Ofast","-funroll-loops","-fdelete-null-pointer-checks")
#pragma GCC target("ssse3","sse3","sse2","sse","avx2","avx")
#define int long long
HohleFeuerwerke;
inline int read(){
	int s=0,f=1;char c=getchar();
	for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
	for(;isdigit(c);c=getchar()) s=s*10+c-'0';
	return s*f;
}
inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x>=10) write(x/10);
	putchar('0'+x%10);
}
const int MAXN=1e6+5; int pri[MAXN],phi[MAXN],tot;
bitset<MAXN>ispri; int Sphi[MAXN];
inline void init(){
    ispri[1]=phi[1]=true;
    for(int i=2;i<=MAXN-5;i++){
        if(!ispri[i]) pri[++tot]=i,phi[i]=i-1;
        for(int j=1;j<=tot&&i*pri[j]<=MAXN-5;j++){
            ispri[i*pri[j]]=true;
            if(i%pri[j]) phi[i*pri[j]]=phi[i]*phi[pri[j]];
            else{phi[i*pri[j]]=phi[i]*pri[j];break;}
        }
    }
    for(int i=1;i<=MAXN-5;i++) Sphi[i]=Sphi[i-1]+phi[i];
}
inline int gcd(int x,int y){
	return __gcd(x,y);
}
inline bool check(int w,int x){
	if(Sphi[w]>=x) return true;
	else return false;
}
signed main()
{
	int T=read(); init();
	while(T){
		int n=T;
		if(n<3){printf("%lld/%lld\n",n-1,1);T=read(); continue;}
		else n-=2;
		int l=2,r=MAXN-1,mid;
		while(l<r){
			mid=(l+r)/2;
			if(Sphi[mid]<=n) l=mid+1;
			else r=mid;
		}
		int lft=n-Sphi[r-1],i;
		for(i=1;i<=r&&lft>=0;i++) if(gcd(i,r)==1) lft--;
		write(i-1),putchar('/'),write(r),puts("");
		T=read();
	}
}
posted @ 2021-01-22 21:34  _HofFen  阅读(146)  评论(0编辑  收藏  举报