[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
",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;
}
原文地址:https://www.cnblogs.com/Rain142857/p/11834614.html