题目描述
下面的图形是著名的杨辉三角形:
如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列: 1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,⋯1,1,1,1,2,1,1,3,3,1,1,4,6,4,1,⋯
给定一个正整数 N,请你输出数列中第一次出现 N 是在第几个数?
输入描述
输入一个整数 N。
输出描述
输出一个整数代表答案。
输入输出样例
示例 1
输入
6
输出
13
评测用例规模与约定
对于 2020 的评测用例,1≤≤N≤10;
对于所有评测用例,1≤N≤1000000000。
思路
由于测试股用例为10^9,因此暴力枚举是行不通的
根据观察,杨辉三角形是沿着中间对称轴向左向右递减的,因此知道了左半边,就一定知道右半边
由于杨辉三角形数值是从上到下递增,从左到右递增,因此,要查找的元素第一次出现的位置一定在右下角的区域
由此可得,遍历顺序应该从最大的从下往上遍历
根据杨辉三角形的组合关系
得到每一斜行的第1个值是C(2k,k)(k为所在斜行,k从1开始)(C是组合数的表示)
测试用例为10^9,根据组合数的计算C(34,17)为23336022>10^9(这是第17斜行开头的数)
C(32,16)为601080390<10^9(这是第16斜行开头的数),因此只需枚举到第16斜行即可
扩展:excel求组合数和排列数
=COMBIN()计算组合
=PERMUT()计算排列
先对测试用例1作特殊情况,当输入为1时,它第一次出现的位置为1
对于其他测试用例,从第16斜行开始向上寻找,由于每一斜行是单调递增的,所以对每一斜行进行二分查找,若找到则返回其位于该斜行的索引位置(如在第2斜行(6,10,15,21,28...)查找15,返回索引2)没找到则继续查找上一斜行
斜行查找具体代码如下
//特殊情况,若找的是1出现的位置,则直接返回1即可
if(num==1){
cout<<"1"<<endl;
}
else{
for(int k=16;k>=1;k--){
long long pos=binarySearch(k,num);//对第k斜行二分查找
if(pos>0){
cout<<pos<<endl;
break;
}
}
}
二分查找
由于第k斜行开头元素值为C(2k,k),末尾元素为C(num,k)
第k斜行,其上标都为k
为什么末尾元素为C(num,k)?
因为对于第1斜行而言,每个元素是上一元素加1(2,3,4,5,6,7,8, ......x)
某一值最早出现在三角形的内部,最晚出现在第1斜行依次+1最后加到值为num时,对于该行元素,其组合数的下标都为num
因此left=2*k,right=num
二分查找具体代码如下
long long binarySearch(int k,long long num){
//对于第k斜行,最小的数是C(k,2*k),最大的数是C(k,num)
int left=2*k,right=num;
while(left<=right){
int mid=(right+left)/2;
long long res=C(mid,k,num);//判断该位置的组合数是否是要找的值
if(res>num){
right=mid-1;
}
else if(res<num){
left=mid+1;
}
else{
//找到了,继续寻找它所在位置
long long pos=find_pos(mid,k);
return pos;
}
}
//未找到,返回-1
return -1;
}
组合数的计算
计算a中取b的组合数,num是当前要查找的值,
当计算到大于num时,会直接返回一个比num大的中间值
这里计算不能采用先乘完再除的做法,会溢出
采用的是边乘边除,这里的除法是可以整除的
原理是连续的2个数中一定有一个数能被2整除,
连续的3个数中一定有一个数能被3整除,以此类推
组合数代码如下
//计算组合数
long long C(int a,int b,long long num){
long long res=1;
for(int i=a,j=1;j<=b;i--,j++){
res=res*i/j;
//如果此时组合数结果已经大于num,则不满足要求,不必继续求出答案了
if(res>num){
break;
}
}
return res;
}
求索引值
找到C(mid,k)==num,说明待查找的值第一次出现的位置已经找到,该值在第k斜行索引值为mid的位置(索引值从0开始)
如在第2斜行(6,10,15,21,28...)查找15,返回索引2
索引值index的求法
由于第k斜行第一个元素为C(2k,k),则第0个元素为在第k斜行的起始位置为2k,第1个元素在第k斜行的位置为2k+1
现已知第k斜行的第mid个元素,则在第k斜行的位置为mid-2*k
求索引值代码如下
long long index=mid-2*k;//第k斜行的索引(从0开始)
找出现的位置
找到索引值后,就是要找num在第几个位置出现了
先找出num前面有几行,可依据所在斜行k进行查找
由于杨辉三角形第1横行1个,第2横行2个,以此类推,运用等差公式 ,求得前rows_before有几个元素,
在加上num所在位置(在第k斜行,则num在其横行的k+1个位置),即为最后要求的第一次出现num所在位置
查找位置代码如下
long long find_pos(int mid,int k){
long long index=mid-2*k;//第k斜行的索引(从0开始)
long long rows_before=2*k+index;//计算num所在行前面有多少行
long long numsBeforeCurrentRow=(1+rows_before)*rows_before/2;//第1横行1个,第2横行2个,以此类推,运用等差公式
return numsBeforeCurrentRow+k+1;//位于第几斜行则num位于该横行的k+1个位置
}
完整代码如下(ACM模式)
#include<iostream>
using namespace std;
//计算组合数
long long C(int a,int b,long long num){
long long res=1;
for(int i=a,j=1;j<=b;i--,j++){
res=res*i/j;
//如果此时组合数结果已经大于num,则不满足要求,不必继续求出答案了
if(res>num){
break;
}
}
return res;
}
long long find_pos(int mid,int k){
long long index=mid-2*k;//第k斜行的索引(从0开始)
long long rows_before=2*k+index;//计算num所在行前面有多少行
long long numsBeforeCurrentRow=(1+rows_before)*rows_before/2;//第1横行1个,第2横行2个,以此类推,运用等差公式
return numsBeforeCurrentRow+k+1;//位于第几斜行则num位于该横行的k+1个位置
}
long long binarySearch(int k,long long num){
//对于第k斜行,最小的数是C(2*k,k),最大的数是C(num,k)
int left=2*k,right=num;
while(left<=right){
int mid=(right+left)/2;
long long res=C(mid,k,num);//判断该位置的组合数是否是要找的值
if(res>num){
right=mid-1;
}
else if(res<num){
left=mid+1;
}
else{
//找到了,继续寻找它所在位置
long long pos=find_pos(mid,k);
return pos;
}
}
//未找到,返回-1
return -1;
}
int main(){
long long num;//待查找的数
cin>>num;
//特殊情况,若找的是1出现的位置,则直接返回1即可
if(num==1){
cout<<"1"<<endl;
}
else{
for(int k=16;k>=1;k--){
long long pos=binarySearch(k,num);//对第k斜行二分查找
if(pos>0){
cout<<pos<<endl;
break;
}
}
}
}
小白终于弄懂了www,感谢老师