【C++】蓝桥杯第十二届省赛杨辉三角形(小白易懂)

题目描述

下面的图形是著名的杨辉三角形:

如果我们按从上到下、从左到右的顺序把所有数排成一列,可以得到如下数列: 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,感谢老师

  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值