EOJ Monthly 2020.3 D.钢琴演奏家

EOJ Monthly 2020.3 D.钢琴演奏家

Cuber QQ 在疫情期间已经宅在家两个月了。

实在是无所事事的他,决定重操旧业,继续实现他曾经梦寐的钢琴演奏家梦想。

掀开积满了灰尘的钢琴盖,是他许久都未触碰的琴键,按下的瞬间,他发现,钢琴坏了。

Cuber QQ 有一个多年的弹奏习惯,他弹奏钢琴,同一时刻一定会同时按下 m 个琴键,他喜欢不同音调交织在一起的声音,可是现在不允许了。

可能是因为时间的原因,钢琴不支持琴键并行(音乐带师 Cuber QQ 发明的词汇)了。通俗来说,当 Cuber QQ 同时按下 m 个琴键的时候,钢琴只会发出音调最高的那个琴键的声音。

不甘心的 Cuber QQ 开始尝试每一个 m 键的组合。他会记录下每一次钢琴发出的音调,他会统计所有演奏出的音调之和,为了验证自己有没有算错,他邀请你来帮他再算一遍。

需要注意的是,因为钢琴坏了,所以可能存在相同音调的琴键。

由于这个和可能会很大,你只需要告诉 Cuber QQ 这个和模 1e9+7 的结果是多少。

输入格式

输入数据第一行包含一个整数 T (1≤T≤1000) 表示数据组数。

对于每一组数据,第一行包含两个整数 n, m (1≤m≤n≤1e6) ,分表表示钢琴的琴键数量和每次同时按下的琴键个数。

第二行包含 n 个整数 a1,a2,…,an (0≤ai≤1e9),表示琴键的音调(可能会出现相同的音调)。

保证对于所有数据有 ∑n≤1e6 。

输出格式

对于每组数据输出一行,包含一个整数表示答案。

由于答案可能很大,需要对 1e9+7 取模。

样例

input

1
3 2
1 2 3

output

8

这题公式其实很好推,答案就是 a n s = ∑ i = m n C i − 1 m − 1 ans=\sum_{i=m}^nC_{i-1}^{m-1} ans=i=mnCi1m1 (PS:注意特判 m = 1 m=1 m=1 的情况),难点就在于每次循环都要算一次组合数 C n m = n ! ( n − m ) ! m ! C_n^m=\frac{n!}{(n-m)!m!} Cnm=(nm)!m!n!,对一个组合数,阶乘我们可以轻松预处理,但是对分母我们只能想到逆元,如果每次循环都求一次的话(PS:我还加了快读),结果T70,可见还不够快,所以我们想到了预处理逆元,也即求 I [ i ] = 1 i ! I[i]=\frac{1}{i!} I[i]=i!1,此时不难发现我们只用求一次最大的那个数,小数可以通过递归求解,比如 I [ i − 1 ] = I [ i ] ∗ i I[i-1]=I[i]*i I[i1]=I[i]i,那么一个组合数 C n m = n ! ( n − m ) ! m ! = F [ n ] ∗ I [ n − m ] ∗ I [ m ] C_n^m=\frac{n!}{(n-m)!m!}=F[n]*I[n-m]*I[m] Cnm=(nm)!m!n!=F[n]I[nm]I[m],我保留了快读(不过没有应该也能A),AC代码如下:

#include
using namespace std;
typedef long long ll;
const int N=1e6+5;
const ll mod=1e9+7;
ll a[N],F[N],I[N];
ll f(ll a,ll b)//快速乘
{
    ll k=0;
    while(b)
    {
        if(b&1) k=(k+a)%mod;
        a=(a+a)%mod;
        b>>=1;
    }
    return k;
}

ll power(ll a,ll b){return b?power(a*a%mod,b/2)*(b%2?a:1)%mod:1;}
void init(){
    F[0]=1;
    ll s=1;
    for(int i=1;i<=N;i++){
        s=f(s,i);
        F[i]=s;
    }
    I[N]=power(F[N],mod-2);
    for(int i=N;i--;){
        I[i]=f(I[i+1],i+1);
    }
}

ll C(ll n, ll k){
    return f(f(F[n],I[n-k]),I[k]);
}

inline ll read() {
	ll s = 0, w = 1;
	char c = getchar();
	for (; !isdigit(c); c = getchar()) if (c == '-') w = -1;
	for (; isdigit(c); c = getchar()) s = (s << 1) + (s << 3) + (c ^ 48);
	return s * w;
}

int main(){
    int t;
    t=read();
    init();
    while(t--){
        int n,m;
        ll ans=0,cnt=1,sum=0;
        n=read();m=read();
        for(int i=0;i<n;i++){
            a[i]=read();
            sum=(sum+a[i])%mod;
        }
        sort(a,a+n);
        for(int i=m-1;i<n;i++){
            ans=(ans+f(a[i],C(i,m-1)))%mod;
        }
        if(m==1) printf("%lld\n",sum);
        else printf("%lld\n",ans);
    }
    return 0;
}

你可能感兴趣的:(EOJ,数论,思维)