Educational Codeforces Round 113 C. Jury Meeting (思维+组合数学)

该博客探讨了一道编程竞赛题目,涉及动态规划和组合数学的应用。问题要求计算一种特殊序列(好序列)的数量,其中序列中的每个元素在减到0之前不能连续减少两次。博主分析了三种不同情况,并提供了详细的解决方案和代码实现,包括最大值等于次大值、大于次大值两单位以及仅有一个最大值的情况。通过递推公式和组合计数,博主给出了计算非法序列的方法,从而得出合法序列的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

https://codeforces.com/contest/1569/problem/C

题意:

n个数,每经过一个数它就-1,如果当所有数减完的时候没有一个数连续减了两次那这样一个序列就叫好序列,求所有好序列的数量,答案mod998244353

题解:

我们可以发现不管n是多少,到最后我们都只用看只剩下最后两个数的情况,而剩下的两个数肯定是最大数和次大数.

所以我们可以分以下三种情况讨论:

1.最大数=次大数

和位置无关,所有排列的序列都是好序列,答案为n!

2.最大数>=次大数+2

所有序列都不是好序列,答案为0

3.最大数只有一个,且次大数和最大数的差值为1

我们通过观察可以发现,至少有一个次大数要在最大数的右边才是好序列,那么答案可以为全排列减去次大数全都在最大数左边的情况.

次大数圈都在最大数左边的方案数可以通过枚举最大数的位置来计算:

我们设有cnt个次大数,那么最大数就可以放在n~cnt+1的位置上.设最大数的位置为i,最大数左边的空位就是i-1个,右边的空位就是n- i个,次大数只能放在左边的位置,所以有(cnti−1)\binom{cnt}{i-1}(i1cnt)种放法,现在已经放了cnt个次大数和一个最大数,所以还剩下n-cnt-1个数,剩下的数可以随意放所以就是(n−cnt−1)!(n-cnt-1)!(ncnt1)!放法.

(n−cnt−1)!∗(cnti−1)(n-cnt-1)!*\binom{cnt}{i-1}(ncnt1)!(i1cnt)就是最大数放在第i个位置时不合法的方案数,枚举i从n~cnt+1,依次减掉即可.

代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<stack>
#include<map>
#include<unordered_map>
#include<set>

#pragma GCC optimize(2)
using namespace std;
#define ll long long
#define PII pair<int,int>
#define PLL pair<ll,ll>
#define fi first
#define se second
#define pb push_back
#define debug(a) cout << #a << " " << a << '\n';
const int N = 2e5 + 5;
const int M = 1e5 + 5;
const int INF = 0x3f3f3f3f;
const ll LLINF = 0x3f3f3f3f3f3f3f3f;
const ll mod = 998244353;

inline ll read();

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;
}

int n, m, t;
ll a[N];
ll pre[N];//pre[i]=1*2*...*i
ll A(ll nn, ll mm) {
    return pre[nn] * qpow(pre[nn - mm], mod - 2) % mod;
}

void solve() {
    cin >> n;
    ll maxx = -1;

    for (int i = 0; i < n; i++) {
        cin >> a[i];
    }
    sort(a, a + n);
    if (a[n - 1] >= a[n - 2] + 2)cout << 0 << '\n';
    else if (a[n - 1] == a[n - 2])cout << pre[n] << '\n';
    else {
        int cnt = 0;
        for (int i = 0; i < n; i++) {
            if (a[i] == a[n - 2])cnt++;//最大值不能在所有次大值的最右边
        }
        ll ans = pre[n];
        for (int i = n; i >= cnt + 1; i--) { //i是最大值的位置 枚举i所在位置不是好序列的方案数
            ll res = (A(i - 1, cnt) * pre[n - cnt - 1]) % mod;
            //cout<<res<<'\n';
            ans = (ans - res + mod) % mod;
        }
        cout << ans % mod << '\n';

    }

}

int main() {
    //ios::sync_with_stdio(false);
    t = read();
    pre[0] = 1;

    for (int i = 1; i <= 2e5; i++) {
        pre[i] = (pre[i - 1] * i) % mod;
    }
    while (t--) {
        solve();
    }

    return 0;
}


inline ll read() {
    char ch = getchar();
    ll p = 1, data = 0;
    while (ch < '0' || ch > '9') {
        if (ch == '-')p = -1;
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        data = data * 10 + (ch ^ 48);
        ch = getchar();
    }
    return p * data;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值