@description@

询问第 k 小的分子分母 ≤ n 的既约分数。

Input

第一行包含一个整数 T(T≤10^2),表示数据组数。

接下来 T 行每行两个整数 n, k,表示一组询问。保证询问的答案在 (0,1] 范围内。

Output

输出 T 行,每行包含一个分数 p/q,要求 gcd(p, q) = 1。

Sample Input

5

4 6

5 1

9 9

3 4

7 11

Sample Output

1/1

1/5

1/3

1/1

3/5

Hint

杭电支持 __int128。

@solution@

寻找第 k 小,不难想到使用二分法。

则假如二分到一个值 x,≤ x 的既约分数可以用如下式子计算:

\[ans = \sum_{i=1}^{n}\sum_{j=1}^{\lfloor x*i\rfloor}[gcd(i, j) = 1]
\]

对其进行反演可得:

\[ans
= \sum_{i=1}^{n}\sum_{d=1}^{d|i}\mu(d)*\lfloor\frac{x*i}{d}\rfloor \\
= \sum_{a*b\le n}\mu(a)*\lfloor x*b \rfloor \\
= \sum_{i=1}^{n}\mu(i)*\sum_{j=1}^{\lfloor\frac{n}{i}\rfloor}\lfloor x*j\rfloor\]

后面那一项如果以 j 为横坐标,x 为常数,可以看成直线 y = x*j 下的整点数量,联想到类欧几里得算法。

但是 x 是个实数的话类欧几里得是不可行的。不过假如我们二分直接使用分数进行二分的话,就的确可以使用类欧几里得算法。

加上预处理莫比乌斯函数前缀和 + 整除分块的话,可以在 \(O(\sqrt{n}*\log n)\) 的时间内完成一次计算。

实数二分需要一个迭代次数,那么我们多少次才能保证精度呢?

假如 a/b 与 c/d 都是两个分子分母 ≤ n 的不相等的分数,则 a/b - c/d = (ad - bc)/(b*d) 最小时,分子 ad - bc = 1,分母 b*d = n*n。

所以精度的一个上限是 1/(n*n),于是取 log(n*n) 次二分就可以保证精度。这样最大次数为 40。

下一个问题是,我们如何找到 > p/q 且最小的既约分数呢?

我们可以在 SB tree(Stern-Brocot Tree)上进行寻找。这棵树在具体数学上有介绍它的性质,WC2019 上也有讲一点。

这里有一篇简略的博客

我们这道题只需要知道 SB Tree 的生成方式,以及它的几个性质:

(1)SB Tree 是一个既约分数的二叉搜索树。

(2)SB Tree 父亲的分母小于儿子的分母。

而这些足以让我们实现我们的目的。寻找答案的这个过程是 O(n)。

@accepted code@

#pragma comment(linker, "/STACK:102400000,102400000")
#include<cstdio>
#include<cmath>
typedef long long ll;
const int MAXN = 1000000;
__int128 gcd(__int128 a, __int128 b) {return (b == 0) ? a : gcd(b, a % b);}
struct frac{
__int128 a, b;
frac(__int128 _a=0, __int128 _b=0):a(_a), b(_b) {}
friend bool operator < (frac a, frac b) {return a.a*b.b < b.a*a.b;}
friend frac operator + (frac a, frac b) {
__int128 d = gcd(a.b*b.a + a.a*b.b, a.b*b.b);
return frac((a.b*b.a + a.a*b.b)/d, a.b*b.b/d);
}
friend frac operator / (frac a, __int128 k) {return frac(a.a, k*a.b);}
};
int n; ll k;
bool search_answer(frac p) {
frac ans = frac(1, 1), a = frac(0, 1), b = frac(1, 1);
while( a.b + b.b <= n ) {
frac c = frac(a.a + b.a, a.b + b.b);
if( p < c )
ans = b = c;
else a = c;
}
printf("%d/%d\n", int(ans.a), int(ans.b));
}
ll smiu[MAXN + 5];
__int128 func(__int128 A, __int128 B, __int128 C, __int128 n) {
if( A/C ) return func(A % C, B, C, n) + A/C*(n + 1)*n/2;
if( B/C ) return func(A, B % C, C, n) + B/C*(n + 1);
if( A == 0 ) return 0;
__int128 m = (A*n + B) / C;
if( m == 0 ) return 0;
return m*n - func(C, C - 1 - B, A, m - 1);
}
ll check(frac x) {
ll ans = 0; int p = 1, q = 1;
// printf("? %d %d\n", int(x.a), int(x.b));
while( p <= n ) {
q = p, p = (n/(n/q));
ll s = smiu[p] - smiu[q - 1];
ans += s*func(x.a, 0, x.b, n/p);
// printf("! %lld\n", ans);
p++;
}
return ans;
}
void solve() {
scanf("%d%lld", &n, &k);
int lim = log2(1LL*n*n) + 1;
frac le = frac(0, 1), ri = frac(1, 1);
for(int i=0;i<=lim;i++) {
frac mid = (le + ri) / 2;
if( check(mid) < k )
le = mid;
else ri = mid;
}
search_answer(le);
}
bool nprm[MAXN + 5];
void init() {
for(int i=1;i<=MAXN;i++)
smiu[i] = 1;
for(int i=2;i<=MAXN;i++) {
if( !nprm[i] ) {
for(int j=i;j<=MAXN;j+=i) {
nprm[j] = true;
if( (j/i) % i == 0 )
smiu[j] = 0;
else smiu[j] *= -1;
}
}
}
for(int i=1;i<=MAXN;i++)
smiu[i] += smiu[i-1];
}
int main() {
init();
int T; scanf("%d", &T);
while( T-- ) solve();
}

@details@

算是还不错的一道题,不过涉及到的知识点比较冷门。。。

我才不会说理解类欧几里得算法花了我一个晚上的时间。

找答案的时候还不能在树上递归找,不然会爆栈。正确的操作是迭代找答案。

@hdu - 6584@ Meteor的更多相关文章

  1. [2019HDU多校第一场][HDU 6584][G. Meteor]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6584 题目大意:求所有满足\(0<\frac{p}{q}\leq1, gcd(p,q)=1,p\ ...

  2. 【译】Meteor 新手教程:在排行榜上添加新特性

    原文:http://danneu.com/posts/6-meteor-tutorial-for-fellow-noobs-adding-features-to-the-leaderboard-dem ...

  3. Using View and Data API with Meteor

    By Daniel Du I have been studying Meteor these days, and find that Meteor is really a mind-blowing f ...

  4. HDOJ 2111. Saving HDU 贪心 结构体排序

    Saving HDU Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) Total ...

  5. 【HDU 3037】Saving Beans Lucas定理模板

    http://acm.hdu.edu.cn/showproblem.php?pid=3037 Lucas定理模板. 现在才写,noip滚粗前兆QAQ #include<cstdio> #i ...

  6. hdu 4859 海岸线 Bestcoder Round 1

    http://acm.hdu.edu.cn/showproblem.php?pid=4859 题目大意: 在一个矩形周围都是海,这个矩形中有陆地,深海和浅海.浅海是可以填成陆地的. 求最多有多少条方格 ...

  7. HDU 4569 Special equations(取模)

    Special equations Time Limit:1000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u S ...

  8. HDU 4006The kth great number(K大数 +小顶堆)

    The kth great number Time Limit:1000MS     Memory Limit:65768KB     64bit IO Format:%I64d & %I64 ...

  9. HDU 1796How many integers can you find(容斥原理)

    How many integers can you find Time Limit:5000MS     Memory Limit:32768KB     64bit IO Format:%I64d ...

随机推荐

  1. python基础--反射、元类、单例设计模式

    反射:reflect,反射指的是一个对象应该具备可以检测.修改.增加自身属性的能力,反射就是通过字符串操作属性 hasattr(对象,带查询的属性名称) 判断某个对象中是否存在某个属性 getattr ...

  2. SVG 动态添加元素与事件

    SVG文件是由各个元素组成.元素由标签定义,而标签格式即html的元素定义格式.但是载入一个SVG文件,却无法通过常规的js获取对象方式来获取到SVG中定义的元素,更无法通过这种方式来动态添加SVG元 ...

  3. python实例 输出字符串和数字

    但有趣的是,在javascript里我们会理想当然的将字符串和数字连接,因为是动态语言嘛.但在Python里有点诡异,如下: #! /usr/bin/python a=2 b="test&q ...

  4. 洛谷P1470 最长前缀

    P1470 最长前缀 Longest Prefix 题目描述 在生物学中,一些生物的结构是用包含其要素的大写字母序列来表示的.生物学家对于把长的序列分解成较短的序列(即元素)很感兴趣. 如果一个集合 ...

  5. django中模型

    一.django需要使用数据库,则需要安装对应的驱动,比如mysql,则需要安装mysqlclient驱动: pip install mysqlclient 二.在settings.py文件中配置数据 ...

  6. H5C3--拖拽接口的使用和实例

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  7. Ubuntu小知识:更改主机名

    Linux主机名是在安装Linux操作系统的过程中设定的,并作为网络中的某一台主机的唯一标志,但是在安装好Linux系统后,如果想修改主机名,该怎么办呢?本文介绍基于Ubuntu Desktop 9. ...

  8. R语言分类算法之随机森林

    R语言分类算法之随机森林 1.原理分析: 随机森林是通过自助法(boot-strap)重采样技术,从原始训练样本集N中有放回地重复随机抽取k个样本生成新的训练集样本集合,然后根据自助样本集生成k个决策 ...

  9. go struct 工厂

  10. Python中 sys.argv的用法简明解释

    Python中 sys.argv[]的用法简明解释 sys.argv[]说白了就是一个从程序外部获取参数的桥梁,这个“外部”很关键,所以那些试图从代码来说明它作用的解释一直没看明白.因为我们从外部取得 ...