组合数
-
首先确定这个数字是怎么选的?数字是可以任意选择的,因此就需要想到组合数来求解。
-
最大值和最小值的差为 k k k 的情况下,我们可以枚举其中一个值,也就是说我们可以枚举最小值这样的话最大值也就确定。
-
确定最小值之后,为了确定最大值的位置,可以直接开一个桶来存储每一个数字的位置,那么数字 a [ i ] a[i] a[i] 对应的最大值就是 a [ i ] + k a[i] + k a[i]+k 。
-
最小值和最大值位置确定之后,为了方便枚举,我们肯定希望这两个位置之间的数字都比最小值大,都比最大值小,因此可以想到将数组进行排序处理。
-
数组排序之后假设枚举到了最小值 a [ i ] a[i] a[i]。那么最大值对应的位置之一假设是 j j j 。那么他们之间的数字个数就是 j − i + 1 j - i + 1 j−i+1 。接下来就是从这 j − i + 1 j - i + 1 j−i+1 个数字中选择 m m m 个数字就好,因为我们已经确定最大值和最小值,因此这里所有数字的个数是 j − i + 1 − 2 j - i + 1 - 2 j−i+1−2 个,还需要选择 m − 2 m - 2 m−2 个数字,因此最小值 a [ i ] a[i] a[i] 对答案的贡献就是 C j − i − 1 − 2 m − 2 C_{j - i - 1 - 2}^{m - 2} Cj−i−1−2m−2 。
-
由于组合数求解需要涉及到逆元的问题,需要提前具备逆元的求解方法。
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 400010;
const int mod = 998244353;
int a[N];
int n, m, k;
vector<int> g[N];
long long fac[N], inv[N];
long long quick_power(long long a, int b) {
long long res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init() {
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i ++ ) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = inv[i - 1] * quick_power(i, mod - 2) % mod;
}
}
long long C(int a, int b) {
if (a < b) return 0;
return fac[a] * inv[b] % mod * inv[a - b] % mod;
}
void solve() {
init();
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
}
sort(a + 1, a + n + 1);
for (int i = 1; i <= n; i ++ ) {
g[a[i]].push_back(i);
}
long long res = 0;
for (int i = 1; i <= n; i ++ ) {
if (a[i] + k <= 200000) {
for (int j : g[a[i] + k]) {
int len = j - i + 1;
if (len < m) continue;
res = (res + C(len - 2, m - 2)) % mod;
}
}
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}
非常的不幸这个代码被 h a c k hack hack 了。
我们可以想象一下,假设 k k k 很小,那么对应的 a [ i ] + k a[i] + k a[i]+k 也会很小,那么可以使得数据中的 a [ i ] + k a[i] + k a[i]+k 达到 1 0 5 10^5 105 级别,同时让达到这个个数的数字也达到 1 0 5 10^5 105 级别,这样的话时间复杂度就是 O ( n 2 ) O(n^2) O(n2) 。
hack数据
感谢白雨龙同学提供的 h a c k hack hack 数据。
正解:
使用容斥原理来做。
- 设 c n t [ i ] cnt[i] cnt[i] 为数字 i i i 出现的个数, p r e [ i ] pre[i] pre[i] 为数组 c n t [ i ] cnt[i] cnt[i] 的前缀和。
- 我们根据上述讨论,为了简化枚举肯定是从小到大枚举最小值,最大值也随之确定。
- 针对于数字 i i i ,那么它所对应的最大值为 i + k i + k i+k ,那么这个区间出现的数字个数为 s u m = p r e [ i + k ] − p r e [ i − 1 ] sum = pre[i + k] - pre[i - 1] sum=pre[i+k]−pre[i−1] ,因此从这些数字中选择 m m m 个数字的总方案为 C s u m m C_{sum}^m Csumm 。这些种方案中肯定包含没有选到最大值 i + k i + k i+k 和最小值 i i i 的情况,我们使用容斥原理将这两种情况减去。那么有:
- 总的方案数 C s u m m C_{sum}^m Csumm。
- 没选最小值 i i i 的方案数 C s u m − c n t [ i ] m C_{sum - cnt[i]}^m Csum−cnt[i]m 。
- 没选最大值 i + k i + k i+k 的方案数 C s u m − c n t [ i + k ] m C_{sum - cnt[i + k]}^m Csum−cnt[i+k]m 。
- 没选最大值且没选最小值的方案数 C s u m − c n t [ i ] − c n t [ i + k ] m C_{sum - cnt[i] - cnt[i + k]}^m Csum−cnt[i]−cnt[i+k]m
那么最后的答案就是 C s u m m − C s u m − c n t [ i ] m − C s u m − c n t [ i + k ] m + C s u m − c n t [ i ] − c n t [ i + k ] m C_{sum}^m - C_{sum - cnt[i]}^m - C_{sum - cnt[i + k]}^m + C_{sum - cnt[i] - cnt[i + k]}^m Csumm−Csum−cnt[i]m−Csum−cnt[i+k]m+Csum−cnt[i]−cnt[i+k]m
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
const int N = 200010;
const int mod = 998244353;
int a[N];
int n, m, k;
int cnt[N], pre[N];
long long fac[N], inv[N];
long long quick_power(long long a, int b) {
long long res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
void init() {
fac[0] = inv[0] = 1;
for (int i = 1; i < N; i ++ ) {
fac[i] = fac[i - 1] * i % mod;
inv[i] = inv[i - 1] * quick_power(i, mod - 2) % mod;
}
}
long long C(int a, int b) {
if (a < b) return 0;
return fac[a] * inv[b] % mod * inv[a - b] % mod;
}
void solve() {
init();
cin >> n >> m >> k;
for (int i = 1; i <= n; i ++ ) {
cin >> a[i];
cnt[a[i]] ++ ;
}
for (int i = 1; i <= 200000; i ++ ) {
pre[i] = pre[i - 1] + cnt[i];
}
long long res = 0;
for (int i = 1; i <= 200000; i ++ ) {
int t = i + k;
if (t <= 200000 && cnt[t] && cnt[i]) {
int num = pre[t] - pre[i - 1];
res = (res + (C(num, m) - C(num - cnt[t], m) - C(num - cnt[i], m) + C(num - cnt[i] - cnt[t], m)) % mod + mod) % mod;
}
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int T = 1;
// cin >> T;
while (T--) {
solve();
}
return 0;
}