题目链接
分析:
题意为给定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((a−b)!∗b!a!)=log(a!)−log((a−b)!)−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;
}