【组合数学】专题笔记目录
这是我做过最难的一题了,真的是推的天昏地暗。
题目大意
现在给你 n 个盒子,每个盒子里放着 ai 个物品,总货物数量 S=∑ni=1ai。
你想通过一系列操作让每个盒子里变成 bi 个物品,满足 ∑ni=1bi=S。
每次操作可以把一个盒子中的一个物品移动到它相邻的盒子中,对 i,i+1 号两个盒子做一次操作的代价是 wi。
显然盒子中的物品数量在任意时刻都不能为负数。
你需要对每一种 b 求出操作代价最小值的和。若设 val(b1,b2,…,bn) 为让第 i 个盒子刚好有 bi 个物品的最小代价,那么你需要求出所有满足 ∑ni=1bi=S 的非负整数数组 b 的 ∑bval(b1,b2,…,bn)。
题解
假设已知 a,b。考虑现在已经让前 i−1 个盒子满足条件,然后需要在 i 和 i+1 之前移动,则显然:ans=n−1∑i=1wi|i∑j=1aj−i∑j=1bj|
考虑先消掉一层循环,观察到 i∑j=1a/bj 都是一个数组的前缀和形式,所以设 c,d 分别为 a,b 的前缀和,在已知 a,b 的情况下答案可以转化为:ans=n−1∑i=1wi|ci−di|
由于 b 未知,因此 d 也是未知的,那么答案就是 ∑dn−1∑i=1wi|ci−di|
观察第二层循环,我们枚举了 i,而由于 ai 已知,所以 ci 也是已知。所以考虑枚举 di 的方案数,这里令 cnt(i,di) 为方案数,则把外层循环扔到里面,答案可以表示为 n−1∑i=1wiS∑di=0|ci−di|×cnt(i,di)。
考虑从 d 数列的性质进行分析,会发现由于 b 是非负整数组,0≤d1≤d2≤⋯≤di⋯≤dn=S。
由于我们枚举 di,并且 dn=S 是已知,所以数列可以分为 [1,i) 和 (i,n)。
接下来考虑求出 0≤d1≤d2≤⋯≤di 解的数量。
于是你会发现这个 ≤ 让你非常难受。怎么办呢?转化一下 【组合数学】8 种盒中放球问题详解,这相当于是第四条,把若干个相同的物品放入几个不同的盒子,盒子允许为空的问题。
这一步转化很重要,我们以 i 为分界点,左边 [1,i) 有 (i−1) 个数的方案数就是 \binom{d_i + i - 1}{i - 1},右边 (i,n) 有 n - i - 1 个数的方案数就是 \binom{S + n - d_i - i - 1}{n - i - 1}。
有了这两个式子,再代入 cnt(i,d_i) 就可以得出答案:
ans=\sum\limits_{i=1}^{n-1} w_i \Bigg( \sum\limits_{d_i=0}^S | c_i - d_i | \times \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1} \Bigg)
由于 |x-y| = 2 \times (x-y) + (y-x)(至于证明,自己手推一下 x-y 的正负性),所以:
ans=\sum\limits_{i=1}^{n-1} w_i \Bigg( 2 \times \sum\limits_{d_i=0}^{c_i} (c_i - d_i) \times \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1} + \sum\limits_{d_i=0}^S (d_i - c_i) \times \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1}\Bigg)
这个式子过于复杂,我们设:
f(n,S,i,k)=\sum\limits_{d_{i}=0}^k \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1}
g(n,S,i,k)=\sum\limits_{d_{i}=0}^k d_i \times \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1}
代回答案,则化简为:
ans=\sum\limits_{i=1}^{n-1} w_i \Bigg( 2 \times c_i \times f(n,S,i,c_i) - 2 \times g(n,S,i,c_i) + g(n,S,i,S) - c_i \times f(n,S,i,S) \Bigg)
但是这仍然不是很好求,所以我们考虑求出 f,g 两个函数的关系。
g(n,S,i,k)=\sum\limits_{d_{i}=0}^k d_i \times \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1}
由于 d_0 = 0 所以忽略这一位的贡献;另外有 C_n^m = C_n^{n-m},由此转化式子:
=\sum\limits_{d_{i}=1}^k d_i \times \binom{d_i + i - 1}{d_i} \times \binom{S + n - d_i - i - 1}{n - i - 1}
把 d_i 和第一个组合数拆成阶乘的式子:d_i \times \frac{(d_i + i - 1)!}{d_i ! (i - 1) !}。
上下同时乘 i:d_i \times \frac{(d_i + i - 1)! \times i}{d_i ! i !}。
化简:i \times \frac{(d_i + i - 1)!}{(d_i-1)! (i-1)!} = i \times \binom{d_i + i - 1}{i}。
代入原式:
g(n,S,i,k)=\sum\limits_{d_{i}=1}^k i \times \binom{d_i + i - 1}{i} \times \binom{S + n - d_i - i - 1}{n - i - 1}
我们当然是要求 g,f 两个函数的关系,所以要把 g 转化为 f 的形式。
观察到 \binom{d_i + i - 1}{i} 中下面的 “i” 并不与上面 “i - 1” 相等,所以考虑把所有的 d_i 都 -1。
同时把公因式 i 提出来。
于是可得:
g(n,S,i,k)=i \times \sum\limits_{d_{i}=0}^{k-1} \binom{d_i + i}{i} \times \binom{S + n - d_i - i - 2}{n - i - 1}
可以转化为 f 的形式:
g(n,S,i,k)=i \times f(n + 1, S - 1, i + 1, k - 1)
这样我们就只要维护 f 函数就行了。先把答案抄过来:
ans=\sum\limits_{i=1}^{n-1} w_i \Bigg( 2 \times c_i \times f(n,S,i,c_i) - 2 \times g(n,S,i,c_i) + g(n,S,i,S) - c_i \times f(n,S,i,S) \Bigg)
=\sum\limits_{i=1}^{n-1} w_i \Bigg( 2 \times c_i \times f(n,S,i,c_i) - 2 \times i \times f(n+1,S-1,i+1,c_i-1) + i \times f(n+1,S-1,i+1,S-1) - c_i \times f(n,S,i,S) \Bigg)
=\sum\limits_{i=1}^{n-1} w_i \Bigg( c_i \times \Big( 2 \times f(n,S,i,c_i) - f(n,S,i,S) \Big) + i \times \Big( f(n+1,S-1,i+1,S-1) -2 \times f(n+1,S-1,i+1,c_i-1) \Big) \Bigg)
这个式子看起来就非常的不可做,但是可以发现当中的 i 和 c_i 都是单调不降的。
所以考虑增量维护这些 f。
显然四个参数中,前两个是定值,所以只需要考虑后两个的变化。现在想如何通过 f(n,S,i,k) 推导出 f(n,S,i,k+1) 和 f(n,S,i+1,k)。
非常显然地,根据定义可以求出 f(n,S,i,k+1)=f(n,S,i,k) + \binom{(k+1)+i-1}{i-1} \times \binom{S-(k+1)+n-i-1}{n-i-1}。即:
f(n,S,i,k+1)=f(n,S,i,k) + \binom{k+i}{i-1} \times \binom{S-k+n-i-2}{n-i-1}
而对于通过 f(n,S,i,k) 推导 f(n,S,i+1,k),我们探究它的组合意义。
由于前面定义 f(n,S,i,k)=\sum\limits_{d_{i}=0}^k \binom{d_i + i - 1}{i - 1} \times \binom{S + n - d_i - i - 1}{n - i - 1},会发现 f(n,S,i,k) 即在坐标轴上只能向右或向上走,从 (0,0) \rightarrow (i-1,d_i) \rightarrow (i,d_i) \rightarrow (n-1,S) 的方案数。
画出示意图(奇丑无比):
由于 d_i \in [0,k] 所以可以发现当从第 k 行走向 k+1 行的时候,点的横坐标(注意!是横坐标)必定大于 i-1。
于是我们转为枚举这个点,f(n,S,i,k) 的组合意义就是 (0,0) \rightarrow (j,k) \rightarrow (j,k+1) \rightarrow (n-1,S) 的方案数(j \in [i,n])。
因此可以重新写出它的表达式:
f(n,S,i,k) = \sum\limits_{j=i}^n \binom{j+k}{j} \times \binom{n+S-j-k-2}{S-k-1}
于是可以推出它的增量转移式:
f(n,S,i+1,k) = f(n,S,i,k) - \binom{i+k}{i} \times \binom{n+s-i-k-2}{S-k-1}
有了上述两个增量转移式,就可以增量维护四个 f 函数求答案了。
为了方便再抄一遍:
f(n,S,i,k+1)=f(n,S,i,k) + \binom{k+i}{i-1} \times \binom{S-k+n-i-2}{n-i-1}
f(n,S,i+1,k) = f(n,S,i,k) - \binom{i+k}{i} \times \binom{n+S-i-k-2}{S-k-1}
代码
非常的好写))
//题解:https://www.acwing.com/solution/content/247598/
#include <bits/stdc++.h>
using namespace std;
const long long N = 3e6 + 15, mod = 998244353;
long long qmi(long long a, long long k) {
long long res = 1;
while (k) {
if (k & 1) res = (res * 1ll * a) % mod;
a = (a * 1ll * a) % mod;
k >>= 1;
}
return res;
}
long long fac[N], inv[N];
void init() {
fac[0] = 1;
for (long long i = 1; i <= 3000000; i++) fac[i] = (fac[i - 1] * 1ll * i) % mod;
inv[3000000] = qmi(fac[3000000], mod - 2);
for (long long i = 2999999; i >= 0; i--) inv[i] = (inv[i + 1] * 1ll * (i + 1)) % mod;
}
long long C(long long n, long long m) {
return ((long long)fac[n] * 1ll * inv[m] % mod * 1ll * inv[n - m]) % mod;
}
struct F {
long long n, S, i, k;
long long res;
void init(long long a, long long b, long long c, long long d) {
n = a, S = b, i = c, k = d;
res = 0;
for (long long di = 0; di <= k; di++) {
res = (res + C(di + i - 1, i - 1) * 1ll * C(S + n - di - i - 1, n - i - 1) % mod) % mod;
}
}
void changei(long long x) {
for (; i < x; i++) {
res += (mod - C(i + k, i) * C(n + S - i - k - 2, S - k - 1) % mod );
}
res %= mod;
}
void changek(long long x) {
for (; k < x; k++) {
res += C(k + i, i - 1) * C(S - k + n - i - 2, n - i - 1) % mod;
}
res %= mod;
}
} f1, f2, f3, f4;
long long T, n;
long long a[N], c[N], w[N];
long long ans;
int main() {
init();
scanf("%lld", &T);
while (T--) {
scanf("%lld", &n);
for (long long i = 1; i <= n; i++) scanf("%lld", &a[i]), c[i] = c[i - 1] + a[i];
for (long long i = 1; i < n; i++) scanf("%lld", &w[i]);
ans = 0; long long S = c[n];
f1.init(n, S, 1, c[1]); f2.init(n, S, 1, S);
f3.init(n + 1, S - 1, 2, S - 1);
f4.init(n + 1, S - 1, 2, c[1] - 1);
for (long long i = 1; i < n; i++) {
f1.changei(i), f2.changei(i), f3.changei(i + 1), f4.changei(i + 1);
f1.changek(c[i]), f4.changek(c[i] - 1);
ans += w[i] * (c[i] * (2ll * f1.res % mod - f2.res + mod) % mod + i * (f3.res - 2 * f4.res + mod) % mod) % mod;
ans = (ans + mod) % mod;
}
printf("%lld\n", ans);
}
return 0;
}
%%%
!!!(~o~)我竟然看不懂!
我觉得我写的很清楚了……哪一步看不懂?我可以解释一下
是完全看不懂……
黑题难,难于走蜀道!
本蒟蒻还得等上不知道多久才能写黑题……
%,其实我觉得这题比骗我呢好想
那题转化成坐标问题我想破头也做不出来
是这样的))
膜拜大佬爆切组合数学 14 题,我才 10……qwq
orz,大佬手搓黑题