D. 钢琴演奏家
https://acm.ecnu.edu.cn/contest/255/problem/D/
单点时限: 1.5 sec
内存限制: 512 MB
Cuber QQ 在疫情期间已经宅在家两个月了。
实在是无所事事的他,决定重操旧业,继续实现他曾经梦寐的钢琴演奏家梦想。
掀开积满了灰尘的钢琴盖,是他许久都未触碰的琴键,按下的瞬间,他发现,钢琴坏了。
Cuber QQ 有一个多年的弹奏习惯,他弹奏钢琴,同一时刻一定会同时按下 m m m 个琴键,他喜欢不同音调交织在一起的声音,可是现在不允许了。
可能是因为时间的原因,钢琴不支持琴键并行(音乐带师 Cuber QQ 发明的词汇)了。通俗来说,当 Cuber QQ 同时按下 m m m 个琴键的时候,钢琴只会发出音调最高的那个琴键的声音。
不甘心的 Cuber QQ 开始尝试每一个 m m m 键的组合。他会记录下每一次钢琴发出的音调,他会统计所有演奏出的音调之和,为了验证自己有没有算错,他邀请你来帮他再算一遍。
需要注意的是,因为钢琴坏了,所以可能存在相同音调的琴键。
由于这个和可能会很大,你只需要告诉 Cuber QQ 这个和模 1 0 9 + 7 10^9+7 109+7 的结果是多少。
输入格式
输入数据第一行包含一个整数
T
(
1
≤
T
≤
1000
)
T (1\le T\le 1000)
T(1≤T≤1000) 表示数据组数。
对于每一组数据,第一行包含两个整数 n , m ( 1 ≤ m ≤ n ≤ 1 0 6 ) n,m(1\le m\le n\le 10^6) n,m(1≤m≤n≤106),分表表示钢琴的琴键数量和每次同时按下的琴键个数。
第二行包含 n n n 个整数 a 1 , a 2 , … , a n ( 0 ≤ a i ≤ 1 0 9 ) a_1,a_2,…,a_n(0\le a_i\le 10^9) a1,a2,…,an(0≤ai≤109),表示琴键的音调(可能会出现相同的音调)。
保证对于所有数据有 ∑ n ≤ 106 ∑n \le 106 ∑n≤106。
输出格式
对于每组数据输出一行,包含一个整数表示答案。
由于答案可能很大,需要对
1
0
9
+
7
10^9+7
109+7 取模。
样例
input
1
3 2
1 2 3
output
8
思路:
每次选
m
m
m个数,那不就是把所有的
m
m
m个数的组合都选一遍,就是排列组合呗。
首先把
n
n
n个数排序,我们就可以这么做:最后一个数选第
i
i
i 个(
m
≤
i
≤
n
m\le i\le n
m≤i≤n),从前
i
−
1
i-1
i−1 个中选
m
−
1
m-1
m−1 个,那么这
m
−
1
m-1
m−1 个数就有
C
i
−
1
m
−
1
C_{i-1}^{m-1}
Ci−1m−1 种选择,所以,最终答案就是
∑
i
=
m
n
(
a
i
∗
C
i
−
1
m
−
1
)
\sum_{i=m}^n(a_i*C_{i-1}^{m-1})
i=m∑n(ai∗Ci−1m−1)
因为
C
n
m
=
n
!
m
!
∗
(
n
−
m
)
!
C_{n}^{m}=\frac{n!}{m!*(n-m)!}
Cnm=m!∗(n−m)!n! ,一开始想的是先处理
n
!
n!
n! ,但是不知道为什么wa(更新,预处理
n
!
n!
n! 已过) ,然后发现选
m
−
1
m-1
m−1 个是固定的,所以可以通过递推来算组合数。
C
i
−
1
m
−
1
=
(
i
−
1
)
!
(
m
−
1
)
!
∗
(
i
−
m
)
!
C_{i-1}^{m-1}=\frac{(i-1)!}{(m-1)!*(i-m)!}
Ci−1m−1=(m−1)!∗(i−m)!(i−1)!
C
i
m
−
1
=
i
!
(
m
−
1
)
!
∗
(
i
−
m
+
1
)
!
C_{i}^{m-1}=\frac{i!}{(m-1)!*(i-m+1)!}
Cim−1=(m−1)!∗(i−m+1)!i!
可以观察到:
C
i
m
−
1
=
C
i
−
1
m
−
1
∗
i
i
−
(
m
−
1
)
C_{i}^{m-1}=C_{i-1}^{m-1}*\frac{i}{i-(m-1)}
Cim−1=Ci−1m−1∗i−(m−1)i 这就是组合数的递推式。
且第一个值一定是 C m − 1 m − 1 = 1 C_{m-1}^{m-1}=1 Cm−1m−1=1。
Code:
// EOJ Monthly 2020.3 D. 钢琴演奏家
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
ll a[maxn];
ll qpow(ll A, ll B)
{
ll res = 1;
while (B)
{
if (B & 1)
res = res * A % mod;
A = A * A % mod;
B >>= 1;
}
return res % mod;
}
ll Inv(ll A, ll P) { return qpow(A, P - 2); } //逆元
int main()
{
int T, n, m;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
ll ans = a[m];
ll temp = 1;
for (int i = m + 1; i <= n; i++)
{
temp = temp * (i - 1) % mod * Inv(i - m, mod) % mod;
ans = (ans % mod + a[i] * temp % mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}
Code2:
预处理
n
!
n!
n! 方法,注意的点应该是取模,每次计算都要模一下。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 5;
const ll mod = 1e9 + 7;
ll a[maxn];
ll fac[maxn];
ll qpow(ll A, ll B)
{
ll res = 1;
while (B)
{
if (B & 1)
res = res * A % mod;
A = A * A % mod;
B >>= 1;
}
return res % mod;
}
ll Inv(ll A, ll P) { return qpow(A, P - 2); } //逆元
int main()
{
fac[0] = 1;
for (int i = 1; i <= 1e6; i++)
fac[i] = fac[i - 1] * i % mod;
int T, n, m;
scanf("%d", &T);
while (T--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i++)
scanf("%lld", &a[i]);
sort(a + 1, a + n + 1);
ll ans = 0;
ll temp = Inv(fac[m - 1], mod);
for (int i = m; i <= n; i++)
{
ll t = fac[i - 1] * temp % mod * Inv(fac[i - m], mod) % mod;
ans = (ans % mod + a[i] % mod * t % mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}