找规律 + 二分 - 杨辉三角形 - 第十二届蓝桥杯省赛第一场C++ B组

找规律 + 二分 - 杨辉三角形 - 第十二届蓝桥杯省赛第一场C++ B组

题意:

下面的图形是著名的杨辉三角形:
在这里插入图片描述
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列:

1, 1, 1, 1, 2, 1, 1, 3, 3, 1, 1, 4, 6, 4, 1, …

给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数?

输入格式

输入一个整数 N。

输出格式

输出一个整数代表答案。

数据范围

对于 20% 的评测用例,1 ≤ N ≤ 10;
对于所有评测用例,1 ≤ N ≤ 109

输入样例:

6

输出样例:

13

分析:
在这里插入图片描述

杨辉三角形的性质:

我们首先将三角形看作是一个三角矩阵。

性质1: 第 i 行 , 第 j 列 的 值 为 C i j ( i ≥ j ) 。 第i行,第j列的值为C_{i}^{j}(i\ge j)。 ijCij(ij)

性质2: 三 角 形 是 对 称 的 , 且 中 间 对 称 轴 上 的 数 字 为 C 2 i i , 其 中 i 是 它 所 在 的 行 号 。 三角形是对称的,且中间对称轴上的数字为C_{2i}^i,其中i是它所在的行号。 C2iii

如 , C 0 0 = 1 , C 2 1 = 2 , C 4 2 = 6 , C 6 3 = 20... \quad\qquad 如,C_0^0=1,C_2^1=2,C_4^2=6,C_6^3=20... C00=1C21=2C42=6C63=20...

性质3: 每 一 列 、 每 一 列 上 的 数 字 均 是 非 递 减 的 。 每一列、每一列上的数字均是非递减的。

本题中:

根据性质2,又因为我们要查找n第一次出现的位置,所以我们可以忽略三角形的右半部分(保留左半部分和中间)

得到的矩阵如下:
在这里插入图片描述
然后我们发现:

矩阵的每一行和每一列,都是递增的,而对称轴上的数,恰是矩阵中每一列的首个数字

所以,我们可以粗略的估计,当列号 j > 16 j > 16 j>16时, C 2 j j > 1 0 9 C_{2j}^{j}>10^9 C2jj>109,那么我们仅需考虑列号 j ∈ [ 0 , 16 ] j∈[0,16] j[016]的情况。

j j j 列数的取值为: C 2 j j 、 C 2 j + 1 j 、 . . . 、 C 2 j + k j , 其 中 k > 1 , C_{2j}^j、C_{2j+1}^j、...、C_{2j+k}^j,其中k>1, C2jjC2j+1j...C2j+kjk>1

此时,又根据性质3,我们可以在第j列上二分查找第一个大于等于 n n n 的数。

所以,我们可以从大到小枚举 j j j,在第 j j j 列上二分查找第一个大于等于 n n n的数,

若其与n相等,则输出其序号;否则,继续在 j − 1 j-1 j1 列上查找这个数。

序号的计算: 假设行号为 r r r,列号为 j j j,前 r − 1 r - 1 r1 行的数字的个数为 r ( r + 1 ) 2 \frac{r(r+1)}{2} 2r(r+1),则序号为 : r ( r + 1 ) 2 + j + 1 \frac{r(r+1)}{2}+j+1 2r(r+1)+j+1

从大到小枚举的原因: 因为我们要找第一个与 n n n 相等的数,即 C r j = n C_r^j=n Crj=n 的第一个数。

也就是满足 C r j = n C_r^j = n Crj=n 所有数中, r 最 小 的 一 个 数 r最小的一个数 r,要 r r r 尽量小,那么 j j j 就尽量大,所以从大到小枚举 j j j

二分的细节:

枚举第 j j j 列,我们二分它的行号 k k k k k k 的下界是 2 j 2j 2j, 上界必然不会超过n(因为第一列的第n行等于n)。


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>

using namespace std;

typedef long long ll;

int n;

ll C(int a, int b)
{
    ll res = 1;
    for(ll up = a, down = 1; down <= b; up --, down ++)
    {
        res = res * up / down;
        if(res > n) return res;
    }
    return res;
}

bool check(int j)
{
    ll l = 2 * j, r = max((ll)n, l);
    while(l < r)
    {
        ll k = l + r >> 1;
        if(C(k, j) >= n) r = k;
        else l = k + 1;
    }
    
    if(C(r, j) != n) return false;
    
    cout<<(r + 1) * r / 2 + j + 1<<endl;
    
    return true;
}

int main()
{
    cin>>n;
    for(int j=16; ;j--)
        if(check(j))
            break;
        
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值