2021 CCPC 威海赛区G题 Desserts

2021 CCPC 威海赛区G题 Desserts

题解

n n n种类型的糖果(第 i i i种糖果有 a i a_i ai个)要分给 k ( k = [ 1 , m ] ) k(k=[1,m]) k(k=[1,m])个队伍,每个队伍在同一种类型的糖果只能拿出一个

现在要将所有的糖果发完,求有多少种方案。

数据范围满足 n , m ≤ 5 ⋅ 1 0 4 , ∑ i = 1 n a i ≤ 1 0 5 n,m\le 5\cdot 10^4,\sum_{i=1}^na_i\le 10^5 n,m5104,i=1nai105

思路

先考虑暴力的做法,当需要分给 k k k个队伍时,结果为 ∏ i = 1 n C k a i \prod_{i=1}^{n}C_k^{a_i} i=1nCkai(从 k k k个队伍中选出 a i a_i ai个队分发第 i i i种糖果),组合数可以做预处理。
暴力去枚举 k k k时,复杂度为 O ( m ⋅ n ) O(m\cdot n) O(mn),会超时。

计算 ∏ i = 1 n C k a i \prod_{i=1}^{n}C_k^{a_i} i=1nCkai时,如果将相同的 a i a_i ai放在一起,答案就变成了 ∏ i = 1 n ′ ( C k a i ) p i \prod_{i=1}^{n'}{(C_k^{a_i})}^{p_i} i=1n(Ckai)pi
例如: C k 1 C k 2 C k 2 C k 3 C k 3 C k 3 = ( C k 1 ) ( C k 2 ) 2 ( C k 3 ) 3 C_k^1C_k^2C_k^2C_k^3C_k^3C_k^3=(C_k^1)(C_k^2)^2(C_k^3)^3 Ck1Ck2Ck2Ck3Ck3Ck3=(Ck1)(Ck2)2(Ck3)3

同时可以注意到题目中有一个条件是: ∑ i = 1 n a i ≤ 1 0 5 \sum_{i=1}^na_i\le 10^5 i=1nai105,那么 n ′ n' n最大为 1 0 5 \sqrt {10^5} 105 。(想了半天也没想到这个条件可以这么用
可以先预处理出来 p p p,然后计算时使用快速幂,这样就可以确保复杂度可以通过题目。

AC的代码

#include <bits/stdc++.h>
#define ll long long
#define pub push_back
#define sz(x) (int)(x).size()
using namespace std;

const int mod = 998244353;
const int N = 1e5 + 10;

ll power(ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = res * a % mod;
        b >>= 1;
        a = a * a % mod;
    }
    return res;
}

ll inv(ll x) { return power(x, mod - 2); }

int n, m, a[N], p[N];
int vis[N];
ll fac[N], inv_f[N];

void init() {
    fac[0] = 1;
    for (int i = 1; i <= N - 10; i++) fac[i] = fac[i - 1] * i % mod;
    inv_f[N - 10] = inv(fac[N - 10]);
    for (int i = N - 11; i >= 0; i--) inv_f[i] = inv_f[i + 1] * (i + 1) % mod;
}

ll C(ll n, ll m) {
    if (m > n) return 0;
    return fac[n] * inv_f[m] % mod * inv_f[n - m] % mod;
}

int main() {
    init();
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];

    vector<int> vec;  // 最多sqrt(1e5)个
    for (int i = 1; i <= n; i++) {
        p[a[i]]++;
        if (vis[a[i]]) continue;
        vis[a[i]] = 1;
        vec.pub(a[i]);
    }

    for (int k = 1; k <= m; k++) {
        ll res = 1;
        for (auto it : vec) res = res * power(C(k, it), p[it]) % mod;

        cout << res << "\n";
    }

    return 0;
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值