题解 - AT_arc212_c ABS Ball

AT_arc212_c [ARC212C] ABS Ball

难度:青(提高)

题目描述

\(N\) 个白球。首先,你需要将每个球涂成红色或蓝色。

然后,你将这 \(N\) 个已被涂成红色或蓝色的球放入 \(M\) 个可区分的盒子之中。

\(a_i\)\(b_i\) 分别表示第 \(i\) 个盒子中红球和蓝球的数量。

请你求出所有放球方式中,\(\prod_{1\leq i \le M}|a_i-b_i|\) 的和,对 \(998244353\) 取模。

这里,两种放球方式不同,当且仅当存在某个 \(i\),使得 \(a_i\)\(b_i\) 不同。

特别地,球互相之间不区分

输入格式

输入从标准输入读取,格式如下:

\(N\)   \(M\)

输出格式

输出答案。

输入输出样例 #1

输入 #1

2 1

输出 #1

4

输入输出样例 #2

输入 #2

5 7

输出 #2

0

输入输出样例 #3

输入 #3

10000000 5000000

输出 #3

965172629

说明/提示

样例解释 1

有三种方式将球放入盒子 1 中。
如果你放入一个红球和一个蓝球,\(|a_1-b_1|=0\)
如果你放入两个红球或两个蓝球,\(|a_1-b_1|=2\)
所以答案为 \(0+2+2=4\)

数据范围

  • \(1 \leq N,M \leq 10^7\)
  • 所有输入值均为整数。

由 ChatGPT 5 翻译

模拟赛时

\(O(N^4)\) DP做法骗了 42pts 然后就没思路了,想不出计数的冗余项在哪里。

思路

赛后听同学讲解思路,第一步是发现对于任意的小球的排列,都可以让红蓝两两抵消,然后剩下每个箱子装 \(x_i\) 个白球,然后此方案的计数直接等于 \(\prod_{i=1}^{M}x_i\)

现在问题转化为外层枚举总共有 \(p\) 对红蓝球,然后它们分配给 \(M\) 个不同的箱子就是 \(\binom{M+p}{p}\) 种方法。内层需要 \(O(1)\) 计算剩下 \(N-2p\) 个白球放进 $M $ 个箱子之后的价值之和。

考虑怎么快速计算这个东西。2026年9月1日13:17:40 刚才又去咨询了一下大佬,首先我们先考虑可以有空的插板,就是 \(\binom{N-2p+M}{M-1}\) 种方案,我们再考虑刚才的估值函数 \(\prod_{i=1}^Mx_i\),这是不是就等价于“从每个箱子里挑一个白球出来”的方案数?你可能会问,这样做不还是要枚举每个箱子里有多少个球吗?

A change in perspective is worth 80 IQ points.
-------- Alan Kay

这个时候我们考虑先选出 \(2M-1\) 个球,再从其中选出 \(M-1\) 个作为板子,其组合意义和上面是完全等价的。而这个非常好算,就是 \(\binom{N-2p+M}{2M-1}\),显然我们保证 \(N-2p\ge M\) 来确保贡献非 \(0\) 即可,或者直接把它的组合数干成 \(0\) 就可以了。记得最后我们发现每个箱子里白球可以统一染色成红色或蓝色,故乘以 \(2^m\)

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int MOD = 998244353;
const int INF = 2e9; // const ll INF = 4e18
const int N = 1e7 + 5, TOT = 2e7 + 5;
ll qpow(ll a, ll b) {
    ll ret = 1;
    while (b) {
        if (b & 1) ret = ret * a % MOD;
        a = a * a % MOD;
        b >>= 1;
    }
    return ret;
}
ll frac[TOT], rev[TOT];
int n, m;
ll C(int n, int m) {
    if (m > n) return 0ll;
    ll ret = frac[n] * rev[n - m] % MOD;
    ret = ret * rev[m] % MOD;
    return ret;
}
ll ans = 0;
int main()
{
    // ios::sync_with_stdio(0);
    // cin.tie(0);
    frac[0] = 1;
    for (ll i = 1; i < TOT; ++i) {
        frac[i] = frac[i - 1] * i % MOD;
    }
    rev[TOT - 1] = qpow(frac[TOT - 1], MOD - 2);
    for (ll i = TOT - 1; i; --i) {
        rev[i - 1] = rev[i] * i % MOD;
    }
    cin >> n >> m;
    for (int p = 0; (p << 1) < n; ++p) {
        if (n - (p << 1) < m) break;
        ll tmp = C(m + p - 1, p);
        (ans += C(n - (p << 1) + m - 1, (m << 1) - 1) * tmp % MOD) %= MOD;
    }
    ans = ans * qpow(2, m) % MOD;
    cout << ans << '\n';
    return 0;
}

细节好多 QwQ

posted @ 2026-09-01 14:10  peter_code  阅读(15)  评论(0)    收藏  举报