UVA 1649 组合数性质
失之毫厘谬以千里
题意: 给出m,求n和k,使得 C n k = m C_n^k=m Cnk=m。
分析:
主要思路
暴力法,枚举n,那样n就得从2枚举到m, 2 ≤ m ≤ 1 0 15 2\leq m \leq 10^{15} 2≤m≤1015 ,这显然不行。
枚举k,利用 C n i k C_{n_i}^k Cnik 是递增的,从而二分找到对应的n。
细节
-
由 于 C n k = C n n − k 由于C_n^k=C_n^{n-k} 由于Cnk=Cnn−k, 因此我们保证k< n 2 \frac{n}{2} 2n, 于是二分n的时候初始lef=2*k
-
k的枚举范围由显然不会很大,利用 C n n / 2 C_n^{n/2} Cnn/2 时,为最大值以终止
-
在对longlong数运算时,尽量不要用乘法,先用除法排除掉,就算不能整除。
ll Com(ll k,ll n)
{
ll now=1;
for(int i=1;i<=k;++i)
{
if(now/i >m/(n-i+1) )return m+1;
//注意这里不能写成 now*(n-i+1)/i>m ,会wa,因为可能会超过ll范围
now=now*(n-i+1)/i; //注意这里不能写成now/i*(n-i+1)
if(now>m)return m+1;
}
return now;
}
-
计算组合数的时候,注意先乘后除。
-
在二分的时候要注意,我设置的是闭区间
while(left<=right) { if(present>target)lright=mid-1; else if(present<target)left=mid+1; else return; }
代码
#include "bits/stdc++.h"
typedef long long ll ;
using namespace std;
struct re{
ll n,k;
bool operator<(const re &it){
if(n!=it.n)return n<it.n;
return k<it.k;
}
void fu(ll nn,ll kk){
n=nn;k=kk;
}
};
re ans[1000];
ll m;
ll Com(ll k,ll n)
{
ll now=1;
for(int i=1;i<=k;++i)
{
if(now/i >m/(n-i+1) )return m+1;
//注意这里不能写成 now*(n-i+1)/i>m ,会wa,因为可能会超过ll范围
now=now*(n-i+1)/i;
if(now>m)return m+1;
}
return now;
}
int main()
{
// freopen("in.text","r",stdin);
int cas;
scanf("%d",&cas);
while (cas--)
{
scanf("%lld",&m);
int cnt=0;
for(ll k=1;;++k)
{
if( Com(k,k*2)>m ) break;
ll lef=k*2,rig=m,mid;
while (lef<=rig)
{
mid=(lef+rig)/2;
ll gain=Com(k,mid);
if(gain>m) rig=mid-1;
else if(gain<m)lef=mid+1;
else {
ans[++cnt].fu(mid,k);
if(mid-k!=k)ans[++cnt].fu(mid,mid-k);
break;
}
}
}
printf("%d\n",cnt);
sort(ans+1,ans+cnt+1);
for(int i=1;i<=cnt;++i)printf("(%lld,%lld)%s",ans[i].n,ans[i].k,i==cnt?"\n":" ");
}
return 0;
}