[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;
}
posted @ 2019-11-11 14:21  Rain_142857  阅读(96)  评论(0编辑  收藏  举报