EOJ Monthly 2020.3 D. 钢琴演奏家(组合数学+费马小定理求逆元)

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(1T1000) 表示数据组数。

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

第二行包含 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(0ai109),表示琴键的音调(可能会出现相同的音调)。

保证对于所有数据有 ∑ n ≤ 106 ∑n \le 106 n106

输出格式

对于每组数据输出一行,包含一个整数表示答案。
由于答案可能很大,需要对 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 min),从前 i − 1 i-1 i1 个中选 m − 1 m-1 m1 个,那么这 m − 1 m-1 m1 个数就有 C i − 1 m − 1 C_{i-1}^{m-1} Ci1m1 种选择,所以,最终答案就是 ∑ i = m n ( a i ∗ C i − 1 m − 1 ) \sum_{i=m}^n(a_i*C_{i-1}^{m-1}) i=mn(aiCi1m1)
因为 C n m = n ! m ! ∗ ( n − m ) ! C_{n}^{m}=\frac{n!}{m!*(n-m)!} Cnm=m!(nm)!n! ,一开始想的是先处理 n ! n! n!但是不知道为什么wa(更新,预处理 n ! n! n! 已过) ,然后发现选 m − 1 m-1 m1 个是固定的,所以可以通过递推来算组合数。
C i − 1 m − 1 = ( i − 1 ) ! ( m − 1 ) ! ∗ ( i − m ) ! C_{i-1}^{m-1}=\frac{(i-1)!}{(m-1)!*(i-m)!} Ci1m1=(m1)!(im)!(i1)!
C i m − 1 = i ! ( m − 1 ) ! ∗ ( i − m + 1 ) ! C_{i}^{m-1}=\frac{i!}{(m-1)!*(i-m+1)!} Cim1=(m1)!(im+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)} Cim1=Ci1m1i(m1)i 这就是组合数的递推式。

且第一个值一定是 C m − 1 m − 1 = 1 C_{m-1}^{m-1}=1 Cm1m1=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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值