第十二届蓝桥杯B组省赛杨辉三角形题解

请添加图片描述
请添加图片描述

题目分析:

有三种方法(第三种才满分过了)
方法一、首先想到的是利用杨辉三角中每个数都是上面两个的和的性质在O(M)复杂度内求出前M项的同时数出数字的所在位置,对于输入数字N最坏情况为O(N^2)。
方法二、其次是利用第m行第n列的值为C(m-1,n-1)的性质,进行搜索,从1开始,小了向下并尝试靠向中间,大了向左,最后可以在O(N)时间复杂度查到
过程如图

1
1  1
1  2  1
1  3  3  1
1  4  6  4  1
1  5  10 10 5  1
1  6  15 20 15 6  1

如果要查5,路径为(1->1->2->3->(4->6)->4->(5->15)->5)

1
1  
   2  
   3  
   4  6
   5  15

代码如下

#include<iostream>
using namespace std;
long long m;//行-1
long long n;//列-1
long long v;//C(m,n)的值
long long temp;//临时变量用来提速
void down(){//下一层
    v=v*(m+1)/(m-n+1);
    ++m;
}
void left(){//左一个
    v=v*n/(m-n+1);
    --n;
}
void center(){//尝试往中间靠
    temp=v*(m-n)/(n+1);//如果右边更大就向右,否则不用动
    if(v<temp){
        v=temp;
        ++n;
    }
}
int main(){
    long long N,ans=0;
    cin >> N;
    v=1;
    m=1;
    n=0;
    while(1){//查找
        if(v<N){
            down();
            center();
        }else if(v>N){
            left();
        }else{
            break;
        }
    }
    //检查位置正确性
    //cout << m+1 << " " << n+1 << endl;
    ans=m*(m+1)/2+n+1;//前面的等差数列求和然后加当前行
    if(ans==2)ans=1;
    cout << ans << endl;
    return 0;
}

方法三、因为除了第一列全1每一列都是单增的,所以可以一列一列用二分查找(保证列数不会太大查1000列100列都行,仔细算算16列就满足要求,代码注释中会给出简要证明)。
因为是跳跃查找,所以要单独求每个C(m,n),因此必须优化C(m,n)的计算函数
因为x的倍数一定会在相邻的x个数中出现,所以连续的x个数相乘一定是x的倍数,这个定理可以用来优化计算函数。
时间复杂度最坏O(log(n))
代码如下

#include<iostream>
using namespace std;
long long N;
long long P(long long m,long long n){//计算m行n列的值
    --m;//P(m,m)=C(m-1,n-1)
    --n;
    n=max(n,m-n);//C(m,n)==C(m,m-n)
    long long ans=1;
    for(long long i=1;i<=(m-n);++i){
        ans*=(n+i);
        ans/=i;//由上文定理一定能除尽
        if(ans>N){//及时退出防止溢出(只用大小关系,不需要准确值)
            return N+1;
        }
    }
    return ans;
}
long long find(long long n){//二分查找第n列
    long long gap=1<<30,m=n;//不存在m不小于n的位置!!!
    while(gap){
        if(P(m,n)<N){//小了往前跑
            m+=gap;
        }else if(P(m,n)>N){//大了间距缩小往前找
            gap/=2;//(8,4,2,1一定能凑出<=15的所有数(就是二进制表示吗))
            m-=gap;
        }else{
            return (m)*(m-1)/2+n;//前面的行等差数列求和然后加当前行
        }
    }
    return (N+1)*(N+1);//找不到就返会一个一定超了的位置,别的如果找到一定排的靠前。
}
int main(){
    long long ans=1;
    cin >> N;
    if(N!=1)//因为第一列全是1,值相等所以不可以用二分查找
        ans=find(2);
    for(int i=3;i<=100;++i){//第N行总和2^n(每行的总和都是上一行的两倍),最大值肯定>(2^n)/n,左侧100列以内绝对足够搜出10^9的数值(因为前文组合数计算优化过了,根本不怕溢出,查几列随便二分查找的速度很快很快)
        ans=min(ans,find(i));//找最小的
    }
    cout << ans << endl;
    return 0;
}
  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值