P1014 [NOIP1999 普及组] Cantor 表

题目链接:https://www.luogu.com.cn/problem/P1014

有理数可枚举

In 1873 Cantor proved the rational numbers countable, i.e. they may be placed in one-one correspondence with the natural numbers.
来自:Georg Ferdinand Ludwig Philipp Cantor

1873 年,Cantor 证明了有理数是可枚举的。可枚举指的是,你可以为集合中的每个元素都分配一个唯一的自然数,建立一个一一对应的关系。

有理数具有 p/q 的形式。p 表示分母,q 表示分子,其中 pq 均为正整数。
我们可以将所有有理数排列在一个表格中,其中 p 表示行,q 表示列,

p/q12311/21/21/322/12/22/333/13/23/3

为了对有理数进行编号,我们需要按照某种方式,将元素排成一条线。这里我们从 1/1 出发,然后移动到 2/1,再往斜上方进行移动。继续按照这种方式,即,我们按照 Z 型线路对沿路的元素进行编号,具体可查看下图,

图片来自:How can the set of the rational numbers be countable if there is no

通过上面的方式,我们为每个有理数都指定了一个编号,下表列出了一部分有理数和其编号,

N1234R1/11/22/13/1

通过编号 N 获取有理数 R

哪一条对角线?

题目中我们需要通过给定的编号 N,来获取对应的有理数 R
为了获取有理数 R,我们首先需要知道 R 位于第几条对角线。通过观察图片,我们发现第一条对角线上的元素个数是 1,第二条对角线上的元素个数是 2,按照这种方式,我们可以得知第 n 条对角线上面的元素个数是 n。我们由此可以得知前 n 条对角线的元素个数总和为 1+2++n,根据求和公式我们可得,

1+2++n=i=1ni=n(n+1)2

假设编号 Nn 条对角线上,那么 N 满足以下的不等式

n(n1)2<Nn(n+1)2n2n<2Nn2+n2Nn2+n0n2+n2N1+1+8N2nn2n<2Nn2n2N<0n<1+1+8N21+1+8N2n<1+1+8N2n[1+1+8N2,1+1+8N2)

我们注意到 [1+1+8N2,1+1+8N2) 的区间长度为 1。这时候,我们考虑两种情况:

  1. 左端点为整数。右端点此时也为整数,所以我们可知 n 的值必为左端点,即 n=1+1+8N2。如果为右端点减去一个极小值 ϵ,再对其向下取整,我们可以得到,n=1+1+8N2ϵ
  2. 左端点为小数。我们可以很容易就知道 n=1+1+8N2, 右端点此时也为小数,如果为右端点减去一个极小值 ϵ(小于左端点和左端点向下取整的差值),再对其向下取整,我们可以得到,n=1+1+8N2ϵ

现在,我们已经得到了两个公式。通过任一公式,我们都可以知道元素位于第几条对角线。

对角线上第几个元素?

下一步,我们需要知道这个元素是对角线上的第几个元素。我们已经知道这个元素位于第 n 条对角线,那么只要将编号 N 减去前 n1 对角线包含的元素个数,就可以得出元素在对角线 n 上的位置 k。结合求和公式,我们得到,

k=Ni=1n1ik=Nn(n1)2

分母和分子

图片来自:How can the set of the rational numbers be countable if there is no

我们按照 Z 型线路对沿路的元素进行编号,奇数对角线我们按照 的方向进行编号,偶数对角线我们则以 方向进行编号。

通过观察,我们发现同一对角线上的分母和分子之和 p+q 等于对角线编号 n+1。如果对角线方向为
则对角线上的第一个元素的分子为 1,对角线上的第 k 个元素的分子为 k;如果对角线方向为 ,则对角线上的第一个元素的分母为 1,对角线上的第 k 个元素的分母为 k。从而我们可以得到如下的公式,

p={(n+1)k,n 是奇数k,n 是偶数

q={(n+1)k,n 是偶数k,n 是奇数

代码

通过上面的公式,我们得到最终的代码,

// https://www.luogu.com.cn/problem/P1014

#include <iostream>
#include <cmath>

int main()
{
    int N, n, k, p, q;

    std::cin >> N;

    n = ceil((sqrt(1+8*N)-1)/2);

    /* Another way to calculate the diagonal number `n' */
    // const double epsilon = 1e-9;
    // n = floor((sqrt(1+8*N)+1)/2-epsilon);

    k = N-n*(n-1)/2;

    if (n&1)
    {
        p = (n+1) - k;
        q = k;
    }
    else
    {
        p = k;
        q = (n+1) - k;
    }

    std::cout << p << '/' << q << std::endl;
    return 0;
}
posted @   Revc  阅读(416)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示