[CF1188E] Problem from Red Panda
[题目链接]
https://codeforces.com/contest/1188/problem/E
[题解]
记 \(x_{i}\) 表示 \(i\) 被选中的次数 , 考虑 \(\{x_i\}\) 合法的条件 , 令 \(s = \sum{x_{i}}\)。
首先需要保证最终数组中的每个元素非负 , 故 \(a_{i} - s + k \cdot x_{i} \geq 0\) , 也就是 \(x_i\ge\lceil\frac{\max(s-a_i,0)}k\rceil\)。
此外 , 还需判断每轮后是否能使得每个元素都非负 , 假设当前是第 \(t\) 轮 , 不妨将每个 \(a_{i}\) 都减去 \(t\) , 再用 \(t\) 个 \(k\) 填补空缺 , 需要满足 \(\sum_{i=1}^k\lceil\frac{\max(t-a_i,0)}k\rceil\le t\) , 在第一个条件的限制下 , 每个元素不会被加超过 \(x_{i}\) 次。
故一个 \(\{x_i\}\) 满足条件当且仅当 :
(1). \(x_i\ge\lceil\frac{\max(s-a_i,0)}k\rceil\)
(2). 对于 \(0\le t\le s\) , 都有 \(\sum_{i=1}^k\lceil\frac{\max(t-a_i,0)}k\rceil\le t\)。
计算 \(\sum_{i=1}^k\lceil\frac{\max(t-a_i,0)}k\rceil\le t\) 是容易的 , 只需对于 \(a_{i} < s\) 的部分开一个桶维护 \(a_{i} mod \ k\) 即可。
考虑当所有 \(x_{i}\) 非负时 , 将其全部减一本质上是同一种情况。
结论 :所有至少有一个数未操作的不同的 \(\{x_i\}\) , 两两本质不同
证明 :
\((1).\) 若操作集合相同 , 若操作次数不同 , 因为有数没被操作 , 故最终得到的序列必然本质不同; 若操作次数相同 , 则必能找到两个数使其得到的结果不同。
\((2).\) 若操作集合不同 , 设 \(i\) 在第一个序列未被操作但在第二个序列中被操作 , \(j\) 在第一个序列被操作但在第二个序列中未被操作。 若操作次数相同 , 那么有 \(\delta a_{i , 1} = -cnt , \delta a_{i , 2} = C \cdot k - cnt \neq \delta a_{i , 1}\)。若操作次数不同 , \(\delta a_{i , 1} = -cnt_{1} , \delta a_{j , 2} = -cnt_{2}\)。 因为 \(a_{j , 1} = C \dot k - cnt_{1} = -cnt_{2}\) , 所以 \(cnt_{1} >cnt_{2}\) , 同理又有 \(cnt_{2} > cnt_{1}\) , 矛盾。
故我们成功地将问题转化为对 \(\{x_i\}\) 计数。不难发现新问题中 \(s\) 的规模被缩小至 \(max\{a_{i}\}\)
也就是说 , 问题变为了 :
有 \(k\) 个变量 , 其中有 \(r\) 个 (\(a_{i} < s\) 的个数) 有一个取值下界 \(lim_{i}\) 且 \(lim_{i} \geq 1\) , 求这 \(k\) 个变量中至少有一个为 \(0\) 且总和为 \(s\) 的方案数。
不妨令 \(w = \sum{lim_{i}}\) 。
考虑容斥原理 , 用总方案数减去至少有一个 \(0\) 的方案数 , 这两者都可以用组合数学中经典的 "插板法" 求解。
答案为 \(\binom{s-w+k-1}{k-1}-\binom{s-w+r-1}{k-1}\)。
故时间复杂度 \(O(max\{a_{i}\} + KlogK)\)
[代码]
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
#define rep(i , l , r) for (int i = (l); i < (r); ++i)
const int MN = 2e6 + 5 , mod = 998244353;
int K , N , A[MN] , fac[MN] , ifac[MN] , cnt[MN];
inline void inc(int &x , int y) {
x = x + y < mod ? x + y : x + y - mod;
}
inline void dec(int &x , int y) {
x = x - y >= 0 ? x - y : x - y + mod;
}
inline int qPow(int a , int b) {
int c = 1;
for (; b; b >>= 1 , a = 1ll * a * a % mod) if (b & 1) c = 1ll * c * a % mod;
return c;
}
inline void init(int n) {
fac[0] = 1;
for (int i = 1; i <= n; ++i) fac[i] = 1ll * fac[i - 1] * i % mod;
ifac[n] = qPow(fac[n] , mod - 2);
for (int i = n - 1; i >= 0; --i) ifac[i] = 1ll * ifac[i + 1] * (i + 1) % mod;
}
inline int binom(int n , int m) {
if (n < m) return 0;
else return 1ll * fac[n] * ifac[m] % mod * ifac[n - m] % mod;
}
int main() {
init(2e6);
scanf("%d" , &K); int cur = 0;
for (int i = 1; i <= K; ++i) scanf("%d" , &A[i]) , N += A[i];
sort(A + 1 , A + 1 + K); int ans = 0;
for (int i = 0 , j = 1; i <= A[K]; ++i) {
while (A[j] < i) ++cnt[A[j++] % K];
cur += cnt[(i - 1 + K) % K];
if (cur > i) {
printf("%d\n" , ans);
return 0;
}
inc(ans , binom(i - cur + K - 1 , K - 1));
if (i - cur + j - 2 >= K - 1)
dec(ans , binom(i - cur + j - 2 , K - 1));
}
printf("%d\n" , ans);
return 0;
}