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\n",ans);
}
return 0;
}