牛客15476-组合数问题2

题目链接
在这里插入图片描述
在这里插入图片描述
分析:

题意为给定n,k,选择k个 C a b C_a^b Cab(0<=a<n,0<=b<=a),使它们的和最大,也就是选择前k个大的 C a b C_a^b Cab
通过组合数可以看到, C a i C_a^i Cai(0<=i<=a)是先递增,再递减的,所以在选数时,应该从中间向两边选择
在这里插入图片描述
在这里插入图片描述
具体做法是先将中间的数存放在优先队列中,每次最大数出队时,向将两边数入队(若出队数在中间,将两边的数入队;若出队数在右边,将右边的数入队;若出队数在左边,将左边的数入队)。存每个数大小时不能直接存,可以对其取 l o g log log, l o g ( a ! ( a − b ) ! ∗ b ! ) = l o g ( a ! ) − l o g ( ( a − b ) ! ) − l o g ( b ! ) log(\frac{a!}{(a-b)!*b!})=log(a!)-log((a-b)!)-log(b!) log((ab)!b!a!)=log(a!)log((ab)!)log(b!)
每次将组合数 C a b C_a^b Cab的对数存入队列作为优先队列排列值即可。

代码如下:

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<cmath>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const int mod=1e9+7;
int n,k;
int f[N],q[N];//f[N]记录逆元,q[N]记录阶乘
double s[N];
int ans=0;
struct node{
    int n, m;
    double s;
    bool operator < (const node & A) const{//运算符重载
        return s < A.s;
    }
};
priority_queue<node> qn;
int qmi(int a,int b,int p)//费马小定理求逆元
{
	int res=1;
	while(b)
	{
		if(b&1) res=(ll)res*a%p;
		a=(ll)a*a%p;
		b>>=1;
	}
	return res;
}
int C(int a,int b)//求组合数
{
	return (ll)q[a]*f[b]%mod*f[a-b]%mod;
}
int main()
{
	scanf("%d%d",&n,&k);
	f[0]=q[0]=1;
	for(int i=1;i<=n;i++)//预处理出所有的阶乘、阶乘的逆元、和对阶乘取对数的值
	{
		q[i]=(ll)q[i-1]*i%mod;
		f[i]=(ll)f[i-1]*qmi(i,mod-2,mod)%mod;
		s[i]=s[i-1]+log(i);
	}
	for(int i=n;i>=0;i--)//将中间的数入队
	{
		if(i%2==0) qn.push(node{i,i/2,s[i]-s[i/2]-s[i-i/2]});
		else
		{
			qn.push(node{i,i/2,s[i]-s[i/2]-s[i-i/2]});
			qn.push(node{i,i/2+1,s[i]-s[i/2+1]-s[i-i/2-1]});
		}
		if(qn.size()>=k) break;
	}
	while(k--)
	{
		node t=qn.top();
		qn.pop();
		int a=t.n,b=t.m;
		ans=((ll)ans+C(a,b))%mod;
//		if(qn.size()>=k) continue;
		if(b*2==a)//在中间,向两边延申
		{
			double sm=s[a]-s[b-1]-s[a-(b-1)];
			qn.push(node{a,b+1,sm});
			qn.push(node{a,b-1,sm});
		}
		else
		{
			if(b*2<a)//在左边,向左边延申
			{
				double sm=s[a]-s[b-1]-s[a-(b-1)];
				qn.push(node{a,b-1,sm});
			}
			else//在右边,向右边延申
			{
				double sm=s[a]-s[b+1]-s[a-(b+1)];
				qn.push(node{a,b+1,sm});
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

chp的博客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值