[CF1188E] Problem from Red Panda [题解]

Problem from Red Panda

题意

给定长为 \(n\) 的自然数序列 \(a_1,a_2……a_n\),每次操作选择满足(对所有 \(j ≠ i\) 都有 \(a_j > 0\))的下标 \(i\),令 \(a_i\) 加上 \(n\) 然后全局减 \(1\),求能够得到的序列数量 \(mod\) \(998244353\)

分析

显然,我们可以把每次操作视为给 \(a_i\) 加上 \(n\),最后全体 \(-1\)

\(\Delta a = a_i^{'} - a_i\),其中 \(a_i^{'}\)\(a_i\) 最后的值,我们可以充要地把计数转化为对不同的 \(\Delta a\) 序列的计数。

按照原题意,不难发现,对于任意时刻,都有 \(\Delta a \ge -a_i\)。如果我们改变题目中的操作,即将其视作给 \(a_i\) 减去 \(n\),然后全局 \(+1\),相应的,此时我们可以得到 \(\Delta a <= a_i\)。(接下来的叙述都是转化后的,即每次操作是 \(a_i - n\),全局 \(+1\)

我们能够发现的一个比较显然的性质是,如果我们对所有 \(a_i\) 都操作了一次,最后得到的序列就是原来的序列。假定对于 \(a_i\) 我们操作了 \(c_i\) 次,如果 \(c_i\) 的最小值不为 \(0\),显然这个操作序列中有一些无用的操作,这个操作序列和我给每个操作减去 \(min(c_i)\) 最后的结果是等价的。所以,必定有 \(a_i\) 没有进行任何操作

同时,有一个显然的结论:如果两个操作序列完全相同,结果也一定相同。那么,如果两个操作序列(存在 \(c_i = 0\))中有一个 \(a_i\) 的操作次数不同,结果是否会相同了?我们尝试证明。

  • 假设两个操作序列操作过的 \(a_i\) 相同。如果操作次数不同,显然结果是不同的,一个较为简单的考虑是总操作次数不同,则没有被操作的数的变化不同;如果操作次数相同,结果也是不同的,同样的考虑是,若一个 \(a_i\) 被操作的次数不同,它们的变化一定不一样(减去相同的数,但加上的数不同)。

  • 假设两个操作序列操作过的 \(a_i\) 不同。设 \(k\) 在第一个中没有被操作但在第二个中被操作,设 \(j\) 定义刚好与 \(k\) 相反。如果操作次数相同为 \(x\),那么在第一个中 \(\Delta a_k = x\),在第二个中 \(\Delta a_j = x\),如果操作后的序列相等,就有在第一个中 \(\Delta a_j = x\),即第一个中不能对 \(j\) 进行操作,与假设不符;若操作次数不同,则有在第一个中 \(\Delta a_k = x_1\),在第二个中 \(\Delta a_j = x_2\),则第一个中 \(a_j = x_1 - c_j \times n = x_2\),有 \(x_1 > x_2\),对 \(k\) 同理分析得到 \(x_1 < x_2\),矛盾。

所以我们就不用再担心去重的问题了。

接下来我们需要考虑的是,一个操作序列什么情况下是合法的?

我们发现,若按照 \(a_i\) 大小升序排列后依次操作之,且能够将所有 \(c_i > 0\) 的数操作一轮,且对于每个点,有 \(cnt - n \times c_i \leq a_i\)(\(cnt\) 表示总操作次数)那么该操作可行。考虑简单证明:

  • 若第一轮即不合法,整个序列显然不合法。

  • 若一个数操作一轮过后合法且还有下一次操作,其显然合法。因为在操作集合里的数的个数小于 \(n\),所以每一轮加上的数一定小于 \(n\),而减去的数则刚好是 \(n\),显然一定是更优的。所以我们只需要关注那些操作次数被用完的数,这里的判定条件依然比较显然,带入到 \(\Delta a_i \leq a_i\),即为 \(cnt - n\times c_i\leq a_i\)

假设枚举 \(cnt\),对于 \(cnt - n\times c_i\leq a_i\) 我们可以显然将其化为 \(c_i \geq max({\lceil \frac{cnt - a_i}{n} \rceil}, 0)\),显然,对于每个 \(cnt\)会有一些数至少被操作若干次,而除此之外的操作次数,我们可以任意分配

注意到 \(c_i\) 在枚举 \(cnt\) 之后,只和 \(a_i\) 的值有关,我们可以将 \(a_i\) 升序排序,满足 \(c_i > 0\) 的数会成为我们的一个前缀。而对于 \(a_i >= cnt\) 的数来说,事实上我们不管怎么操作都不会触碰到下界,所以我们只需要关注这个前缀即可。设前缀的 \(\sum c_i = sum\),那么对于当前 \(cnt\),还能操作 \(cnt - sum\) 次,这些操作即可随便分配给任意一个数且满足至少有一个数没有被操作过

这是个比较经典的组合计数,我们可以将其视为 \(n\) 个球放入 \(m\) 个盒子的方案数减去盒子中有 \(k\) 个不允许为空的方案数,即(这里的 \(k = \sum (c[i] == 0)\)):

\[C_{n + m - 1}^{m - 1} - C_{n - k + m - 1}^{m - 1} \]

然后我们仅需考虑对 \(sum\) 的快速统计计算,主要到对于每个 \(a_i\),每当 \(cnt = k\times n + a_i + 1\) 时,由于除法的上取整,会相较之前的取值增加一个贡献。将相同的 \(a_i\) 合并,然后对于每种 \(a_i\) 暴力标记 \(k\times n + a_i + 1\),之后在值域上做一个前缀和就能得到每种 \(cnt\) 对应的 \(sum\)

最后,在枚举的时候双指针判断一下合不合法即可。

CODE

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10, mod = 998244353;
inline int read()
{
	int s = 0, w = 1;
	char ch = getchar();
	while(ch < '0' || ch > '9') { if(ch == '-') w *= -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') s = s * 10 + ch - '0', ch = getchar();
	return s * w; 
}
int n, mx, ans = 1;
int a[N], cnt[N], sum[N], vis[N];
int fac[N], inv_fac[N];
inline int power(int x, int k)
{
	int res = 1;
	while(k){
		if(k & 1) res = res * x % mod;
		x = x * x % mod, k >>= 1;
	}
	return res;
}
inline void initial()
{
	fac[0] = inv_fac[0] = 1;
	for(register int i = 1; i <= N - 10; i++) fac[i] = fac[i - 1] * i % mod;
	inv_fac[N - 10] = power(fac[N - 10], mod - 2);
	for(register int i = N - 11; i >= 1; i--) inv_fac[i] = inv_fac[i + 1] * (i + 1) % mod;
}
inline int C(int x, int y) { return x < y ? 0 : fac[x] * inv_fac[x - y] % mod * inv_fac[y] % mod; }
signed main()
{
	initial();
	n = read(), mx = n;
	for(register int i = 1; i <= n; i++) a[i] = read(), mx = max(mx, a[i]); 
	for(register int i = 1; i <= n; i++) cnt[a[i]]++;
	sort(a + 1, a + n + 1);
	for(register int i = 0; i <= mx; i++){
		if(!cnt[i]) continue;
		for(register int j = i + 1; j <= mx; j += n) sum[j] += cnt[i];
	}
	for(register int i = 1; i <= mx; i++) sum[i] += sum[i - 1];
	int l = 0, flag = 1;
	for(register int i = 1; i <= n; i++) vis[i] = (a[i] >= (i - 1)); //判断是否能够让左边操作一轮 
	for(register int i = 1; i <= mx; i++){
		while(l < n && (a[l + 1] < i)) l++, flag &= vis[l]; //如果大于总次数 i 没必要放一起 
		if(!flag) break;
		ans = (ans + C(i - sum[i] + n - 1, n - 1) - C(i - sum[i] + n - 1 - (n - l), n - 1)) % mod, ans = (ans + mod) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}
posted @ 2022-02-12 11:20  ╰⋛⋋⊱๑落叶๑⊰⋌⋚╯  阅读(49)  评论(3编辑  收藏  举报