P8058 [BalkanOI2003] Farey 序列 题解

Solution

很多题解的的推导过程过于简单?

首先不难想到二分出第 k 个值 x,只需要判断小于 x 的真分数个数即可。

定义 fi 表示以 i 为分母的小于 x 真分数个数。

得到暴力方程:fi=j=1i×x[gcd(i,j)=1]

正难则反:fi=i×xj=1m[gcd(i,j)=k(k>1)]

套路地把 k 提出来:fi=i×xk=2i×xj=1i×xk[gcd(i,j)=1]

不难发现 j=1i×xk[gcd(i,j)=1]=fik

带入原式得:fi=i×xk=2i×xfik

其实 ik 就是 i 的因数。

所以 fi=i×xd|id<ifd

最终小于 x 的真分数个数即为 cnt=i=1nfi,判断 cnt 是否小于 k 即可。

将答案转化为分数只需要枚举分母即可。

时间复杂度为 O(nnlogV)V 为精度所需的值域。

#include<bits/stdc++.h> 
#define ll long long 
#define x first 
#define y second 
#define il inline 
#define debug() puts("-----") 
using namespace std; 
typedef pair<int,int> pii; 
il int read(){ 
	int x=0,f=1; char ch=getchar(); 
	while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); } 
	while(ch>='0'&&ch<='9') x=(x<<1)+(x<<3)+(ch^48),ch=getchar(); 
	return x*f; 
} 
const int N=4e4+10; 
const double eps=1e-10;  
int n,k; 
int f[N];  
il bool check(double mid){ 
	int cnt=0; 
	for(int i=1;i<=n;i++){ 
		f[i]=(int)i*mid; 
		for(int j=2;j*j<=i;j++){ 
			if(i%j==0){ 
				f[i]-=f[j]; 
				if(j*j!=i) f[i]-=f[i/j]; 
			} 
		} cnt+=f[i]; 
	} return (cnt<k); 
} 
signed main(){ 
	n=read(),k=read(); 
	double l=0,r=1,ans=0; 
	while(r-l>=eps){ 
		double mid=(l+r)/2; 
		if(check(mid)) l=mid,ans=mid; 
		else r=mid; 
	} for(int i=1;i<=n;i++){ 
		double x=(double)i*ans;  
		int j=ceil(x);  
		if(fabs((double)j/i-ans)<=eps){ 
			printf("%d %d\n",j,i); 
			break; 
		} 
	} return 0; 
} 
posted @   Celestial_cyan  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示