第K小分数(二分)
Time limit:10000 ms
Memory limit:262144 kB
OS:Linux
Source:[Offer收割]编程练习赛46
judge:HihoCoder - 1692
描述
给定 个不同的质数 。用它们作为分目可以组成 个分数:
请帮助小Ho求出其中第 小的分数。
Input
第一行包含两个整数 和 。
以下 行每行包含一个质数 。
对于 的数据,
对于 的数据,
Output
输出一个分数表示答案
Sample Input
3 4
2
3
5
Sample Output
1/2
题解
因为给出的 是质数,所以每一个分数对应的小数都是独一无二的,就像只能有 ,而不会有 一样。
而且当分母一定时,分子的大小顺序就是对应的分数的大小顺序,就像
那么如果我们可以知道小于小数 ,且分母为 的分数的个数 的话,我们就可以枚举 个分母求得所有小于 的分数的个数 ,通过比较这个 和 的大小就可以进行二分了,直到最后 。
而每一次求 时我们都保存最接近 的分数的分子和分母,那么二分结束时的分子和分母就是要输出的答案了。
那么怎么获取这个 呢?我们知道当分母为 时,构成的分数是: 和 ,假设 此时为 ,那么把 转化为一个分数且分母为 的最简单的方式就是分子分母同时乘以 : 。所以得到的分数就是 ,显然,分子小于 的有 个,即 向下取整;同理当分母为 时,小于 的分数的个数就是 ,(符号意为向下取整)。
代码
#include <iostream>
#include <algorithm>
#define maxn 1005
#define _for(i, a) for(LL i = 0; i < (a); ++i)
using namespace std;
typedef long long LL;
LL n, k;
LL P[maxn];
LL Numerator, Denominator; //最接近mid的分子和分母
LL find(double mid) {
LL cnt = 0;
_for(i, n) {
LL num = mid * P[i]; //向下取整
cnt += num;
if (Numerator == -1 && Denominator == -1 || Numerator * P[i] < num * Denominator) { //保存分子和分母
Numerator = num;
Denominator = P[i];
}
}
return cnt;
}
LL gcd(LL a, LL b) {
if (a > b) swap(a, b);
return a ? gcd(b % a, a) : b;
}
void sol() {
cin >> n >> k;
_for(i, n) cin >> P[i];
double l = 0, r = 1;
while (1) {
Numerator = -1, Denominator = -1;
double mid = (l + r) / 2;
LL cnt = find(mid);
if (cnt == k) break;
if (cnt > k) r = mid;
else l = mid;
}
LL _gcd = gcd(Numerator, Denominator);
cout << Numerator / _gcd << "/" << Denominator / _gcd << "\n";
}
int main() {
ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
//freopen("in.txt", "r", stdin);
sol();
return 0;
}