多比特杯武汉工程大学程序设计竞赛——最大值最小值

组合数

  1. 首先确定这个数字是怎么选的?数字是可以任意选择的,因此就需要想到组合数来求解。

  2. 最大值和最小值的差为 k k k 的情况下,我们可以枚举其中一个值,也就是说我们可以枚举最小值这样的话最大值也就确定。

  3. 确定最小值之后,为了确定最大值的位置,可以直接开一个桶来存储每一个数字的位置,那么数字 a [ i ] a[i] a[i] 对应的最大值就是 a [ i ] + k a[i] + k a[i]+k

  4. 最小值和最大值位置确定之后,为了方便枚举,我们肯定希望这两个位置之间的数字都比最小值大,都比最大值小,因此可以想到将数组进行排序处理。

  5. 数组排序之后假设枚举到了最小值 a [ i ] a[i] a[i]。那么最大值对应的位置之一假设是 j j j 。那么他们之间的数字个数就是 j − i + 1 j - i + 1 ji+1 。接下来就是从这 j − i + 1 j - i + 1 ji+1 个数字中选择 m m m 个数字就好,因为我们已经确定最大值和最小值,因此这里所有数字的个数是 j − i + 1 − 2 j - i + 1 - 2 ji+12 个,还需要选择 m − 2 m - 2 m2 个数字,因此最小值 a [ i ] a[i] a[i] 对答案的贡献就是 C j − i − 1 − 2 m − 2 C_{j - i - 1 - 2}^{m - 2} Cji12m2

  6. 由于组合数求解需要涉及到逆元的问题,需要提前具备逆元的求解方法。

#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 数据。

在这里插入图片描述

正解:

使用容斥原理来做。

  1. 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] 的前缀和。
  2. 我们根据上述讨论,为了简化枚举肯定是从小到大枚举最小值,最大值也随之确定。
  3. 针对于数字 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[i1] ,因此从这些数字中选择 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 Csumcnt[i]m
  • 没选最大值 i + k i + k i+k 的方案数 C s u m − c n t [ i + k ] m C_{sum - cnt[i + k]}^m Csumcnt[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 Csumcnt[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 CsummCsumcnt[i]mCsumcnt[i+k]m+Csumcnt[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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值