题意:求k个形如(C_a^b)的不同组合数的最大和。限制:(0 leq a leq n),n,k给定。组合数不同是指:a不同或者b不同。
分析:
可以发现我们能用一个堆来维护当前还没算进答案的最大的组合数。
画出杨辉三角形。
1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 2 | 1 | 0 | 0 | 0 | 0 | 0 |
1 | 3 | 3 | 1 | 0 | 0 | 0 | 0 |
1 | 4 | 6 | 4 | 1 | 0 | 0 | 0 |
1 | 5 | 10 | 10 | 5 | 1 | 0 | 0 |
1 | 6 | 15 | 20 | 15 | 6 | 1 | 0 |
1 | 7 | 21 | 35 | 35 | 21 | 7 | 1 |
又可以发现如果(C_i^j)被取出,那么应该往堆里面扔一个(C_{i-1}^j)比较合算。
那么做法就出来了:用堆来维护当前没被算进答案的最大的组合数。
但是不幸的是,数据太大导致stl无法进行运算。
怎么办呢?
可以给组合数的值取个对数,然后神奇的事发生了:
(log C_n^m)
(= log frac{n!}{m!(n-m)!})
(= log {n!} - log {m!} - log {(n-m)!})
(= sum_{i=1}^{n}{log i} - sum_{i=1}^{m}{log i} - sum_{i=1}^{n-m}{log i})
然后惊奇地发现,求一个(log i)的前缀和就可以了。
初始化的时候,把(C_n^0),...,(C_n^n)扔堆里边。
代码:
#include<cstdio>
#include<queue>
#include<algorithm>
#include<cmath>
#include<cstring>
using namespace std;
const long long p=1e9+7;
double logn[1000010];
long long quickpow(long long a,long long k)
{
long long ans=1ll;
while(k)
{
if(k&1)ans=(ans*(a%p))%p;
a%=p;
a=a*a%p;
k>>=1;
}
return ans;
}
long long fac[1000010],inv[1000010];
struct mancity
{
double lg;long long n,m;
friend bool operator <(mancity x,mancity y)
{
return x.lg<y.lg;
}
};
priority_queue<mancity>q;
int main()
{
long long x,y,z,i,j,k;
long long ans=0ll;
scanf("%lld%lld",&x,&y);
fac[0]=1ll;
for(i=1;i<=x;i++)
fac[i]=(fac[i-1]*i)%p;
inv[x]=quickpow(fac[x],p-2);
for(i=x-1;i>=0;i--)
inv[i]=(inv[i+1]*(i+1))%p;
logn[0]=0;
for(i=1;i<=x;i++)
logn[i]=logn[i-1]+log(i);
for(i=0;i<=x;i++)
{
q.push((mancity){logn[x]-logn[x-i]-logn[i],x,i});
//printf("%.8lf %lld %lld
",logn[x]-logn[x-i]-logn[i],1ll*x,1ll*i);
}
for(i=1;i<=y;i++)
{
j=q.top().n;
k=q.top().m;
q.pop();
ans=(ans+1ll*fac[j]*inv[k]%p*inv[j-k]%p)%p;
//printf("%lld ",1ll*fac[j]*inv[k]%p*inv[j-k]%p);
j--;
q.push((mancity){logn[j]-logn[j-k]-logn[k],j,k});
}
printf("%lld
",ans%p);
return 0;
}