CF2030E MEXimize the Score 题解
题面
假设我们将数组 \(b\) 中的元素分割成任意多个 \(k\) 的非空多集 \(S_1, S_2, \ldots, S_k\) ,其中 \(k\) 是一个任意的正整数。定义 \(b\) 的分值为任意整数 \(k\) 的 \(\operatorname{MEX}(S_1)\) \(^{\text{∗}}\) $ + \operatorname{MEX}(S_2) + \ldots + \operatorname{MEX}(S _ k)$ 在 \(b\) 的所有可能分区中的最大值。
给 Envy 一个大小为 \(n\) 的数组 \(a\) 。由于他知道计算 \(a\) 的得分对你来说太容易了,所以他要求你计算 \(a\).\(^{\text{†}}\) 的所有 \(2^n - 1\)$$ 非空子序列的得分之和。由于这个答案可能很大,请输出它对 \(998,244,353\) 取模的结果。
整数集合 \(c_1, c_2, \ldots, c_k\) 中的 \(^{\text{∗}}\) \(\operatorname{MEX}\) 定义为在集合 \(c\) 中不出现的最小非负整数 \(x\) 。例如, \(\operatorname{MEX}([0,1,2,2]) = 3\) 和 \(\operatorname{MEX}([1,2,2]) = 0\) 。
\(^{\text{†}}\) 如果 \(x\) 可以通过删除几个(可能是零个或全部)元素从 \(y\) 得到,那么序列 \(x\) 就是序列 \(y\) 的子序列。
题解
记 \(cnt_i\) 表示数字 \(i\) 在可重集合中出现的次数,对于单个集合,我们很容易知道一个最完美的划分可以达到的分数是 \(cnt_0 + \min(cnt_0+cnt_1) + \ldots + \min(cnt_0+cnt_1+\ldots+cnt_{n-1})\),贪心的考虑把相同的数字分到不同的集合,必定可以构成 \(0\) 到 \(t\) 的一个排列,此时的 \(\operatorname{MEX}\) 和可以达到最大,接下来我们考虑如何求出所有子集的价值和。
不妨设有这样一个集合,这个集合的价值可以通过构造一个 \(0, 1, ..., t\) 排列得到,排列存在当且仅当:
因此我们考虑分成的子集排列分别能得到的价值,可以发现,这个排列中的每一个元素恰好贡献 \(1\),求每个排列中元素的价值总和转换成求每个元素对所有排列的总贡献。
假定最开始所有的集合并为空集,我们逐个插入元素,使得所有集合的并恰好不重复的是 \(S\) 的一个子集。
记 \(f(t) = \sum\limits_{i = k}^{cnt_t}\binom{cnt_t}{i}\),容易发现 \(f(t)\) 相当于把 \(t\) 元素插入 \(i\) 个不同的集合中,相当于把这些元素先插入 \(k\) 个 \(0 \sim t - 1\) 的排列中,这部分元素有 \(1\) 的贡献,剩余的 \(i - k\) 个元素也插入集合中,但是此部分元素没有直接贡献,仅仅可以让集合并变成一个新的子集 ,剩余的 \(r\) 个元素也可以组成 \(2^r\) 个集合,这些集合插入进去也不会影响现有的答案的贡献,因为我们仅仅计算的是元素 \(t\) 产生的贡献,他们可以任意插入已有的排列中,并且不改变 \(t\) 存在的价值。
我们不妨枚举 \(k\),对于每个 \(k\),我们考虑尽可能长的构建 \(k\) 个 \(0 \sim t\) 排列,容易知道最大的 \(t\) 满足 \(\min(cnt_0, cnt_1, \ldots, cnt_{t}) \ge k\),同样,对于每个 \(t\) 我们也需要去计算他的答案。
对 \(0\) 元素来说,拿出 \(k\) 个元素作为合法集合,共有 \(\binom{cnt_0}{k}\) 种不同集合并形式,再考虑将多余的 \(0\) 任意插入进去,这部分的贡献是 \(f(0)\),对于除了 \(0\) 以外的剩余 \(r\) 个元素,可以任意放入集合并且不影响答案,根据乘法原理,贡献是所有操作的种类数的积,此部分的 \(0\) 的答案与并出来的子集数量一致,直接加到答案上即可。
到 \(t\) 元素时,同样,对于前 \(t - 1\) 的元素已经考虑插入完成,有 \(\prod\limits_{i = 0}^{t - 1}f(i)\) 种子集,此时插入方式有 \(f(t)\) 种,对于其后的元素同样考虑是否加入,也有 \(2^r\) 种方式,至此,我们的答案就可以通过上述方案递推求出。
发现 \(\sum\limits_{i = 0}^{n - 1}cnt_{i} = n\),因此时间复杂度为 \(O(n)\),不会超时。
参考代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, mod = 998244353;
int n, a[N], suf[N], pow2[N], frac[N], inv[N];
ll ksm(ll a, ll k)
{
ll res = 1;
while (k)
{
if (k & 1) res = res * a % mod;
k >>= 1;
a = a * a % mod;
}
return res;
}
void init()
{
pow2[0] = frac[0] = 1;
for (ll i = 1; i < N; i ++ ) pow2[i] = 2ll * pow2[i - 1] % mod, frac[i] = frac[i - 1] * i % mod;
inv[N - 1] = ksm(frac[N - 1], mod - 2);
for (ll i = N - 1; i; i -- ) inv[i - 1] = inv[i] * i % mod;
}
int binom(int a, int b)
{
return 1ll * inv[b] * inv[a - b] % mod * frac[a] % mod;
}
void solve()
{
cin >> n;
vector<int> cnt(n);
for (int i = 1; i <= n; i ++ ) cin >> a[i], cnt[a[i]] ++ ;
suf[n - 1] = 1;
for (int i = n - 1; i; i -- ) suf[i - 1] = 1ll * suf[i] * pow2[cnt[i]] % mod;
ll ans = 0, p = 0;
vector<int> f(n), g(n);
g = cnt;
for (int k = n; k; k -- )
{
while (p < n && cnt[p] >= k) p ++ ;
ll res = 1;
for (int i = 0; i < p; i ++ )
{
while (g[i] >= k) (f[i] += binom(cnt[i], g[i])) %= mod, g[i] -- ;
res = res * f[i] % mod;
(ans += res * suf[i] % mod) %= mod;
}
}
cout << ans << endl;
}
int main()
{
init();
int T;
cin >> T;
while (T -- ) solve();
return 0;
}