CF1462E2 Solution

题目链接

题解

容易想到,因为选出的m个数与在原序列中的顺序无关,所以可以将(a)序列排序,然后找到每一个满足最大值与最小值的差(le k)的区间,排列组合选出(m)个数即可。具体实现的问题有两个:找到区间与排列组合。

找到区间:进一步分析可得,我们并不需要这个区间的左右端点,只要知道其中元素的个数。因此可以用(sum)数组记录(a)数组中(le i)的数的个数,因为(a[i]le n)不用离散化,前缀和预处理即可。然后枚举区间的右端点,利用(sum)数组求出(ge a[i]-k)的数的个数。

排列组合:如果在组合函数中每次都计算一遍阶乘的话时间复杂度为(O(n^2)),会TLE。因此需要预处理出所有可能用到的阶乘,为了优化常数,下面的代码中也处理了阶乘的乘法逆元。

AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+10,mod=1e9+7;
int a[N],fac[N],inv[N],sum[N];
//fac[i]:i的阶乘; inv[i]:i阶乘的逆元; sum[i]:a数组中<=i的数的个数
int qpow(int x,int y)
{
	int ans=1;
	while(y)
	{
		if(y&1) ans=(ans*x)%mod;
		x=(x*x)%mod; y>>=1;
	}
	return ans;
}//快速幂(费马小定理求乘法逆元)
void init()
{
	fac[0]=inv[0]=1;
	for(int i=1;i<=N;i++) fac[i]=(fac[i-1]*i)%mod;
	for(int i=1;i<=N;i++) inv[i]=qpow(fac[i],mod-2);
}//预处理
int C(int n,int m) 
{
	if(m>n || m<0) return 0;
	return (((fac[n]*inv[m])%mod)*inv[n-m])%mod;
}//组合函数
signed main()
{
	init();
	int t,k,n,m;
	scanf("%d",&t);
	while(t--)
	{
		scanf("%lld%lld%lld",&n,&m,&k);
		for(int i=0;i<=n;i++) sum[i]=0;
		for(int i=1;i<=n;i++) {scanf("%d",&a[i]); sum[a[i]]++;}
		for(int i=1;i<=n;i++) sum[i]+=sum[i-1];
		sort(a+1,a+n+1);
        //寻找区间+统计答案
		int ans=0;
		for(int i=n;i>=1;i--)
		{
			if(a[i]>k) ans+=C(i-sum[a[i]-k-1]-1,m-1),ans%=mod;
			else ans=(ans+C(i-1,m-1))%mod;
		}
		printf("%d
",ans);
	} 
	return 0;
}
原文地址:https://www.cnblogs.com/violetholmes/p/14220567.html