找规律 + 二分 - 杨辉三角形 - 第十二届蓝桥杯省赛第一场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)。 第i行,第j列的值为Cij(i≥j)。
性质2: 三 角 形 是 对 称 的 , 且 中 间 对 称 轴 上 的 数 字 为 C 2 i i , 其 中 i 是 它 所 在 的 行 号 。 三角形是对称的,且中间对称轴上的数字为C_{2i}^i,其中i是它所在的行号。 三角形是对称的,且中间对称轴上的数字为C2ii,其中i是它所在的行号。
如 , 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=1,C21=2,C42=6,C63=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∈[0,16]的情况。
第 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, C2jj、C2j+1j、...、C2j+kj,其中k>1,
此时,又根据性质3,我们可以在第j列上二分查找第一个大于等于 n n n 的数。
所以,我们可以从大到小枚举 j j j,在第 j j j 列上二分查找第一个大于等于 n n n的数,
若其与n相等,则输出其序号;否则,继续在 j − 1 j-1 j−1 列上查找这个数。
序号的计算: 假设行号为 r r r,列号为 j j j,前 r − 1 r - 1 r−1 行的数字的个数为 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;
}