题目分析:
有三种方法(第三种才满分过了)
方法一、首先想到的是利用杨辉三角中每个数都是上面两个的和的性质在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;
}