[Luogu P4370] 【Code+ #1】组合数问题2
题意:求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\n",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\n",ans%p);
return 0;
}