组合数学杂记
二项式系数
基本结论
对称恒等式:
\[\binom{n}{k} = \binom{n}{n - k} \]加法公式:
\[\binom{n}{m} = \binom{n - 1}{m} + \binom{n - 1}{m - 1} \]对其对应多项式函数求导可以得到:
\[\sum_{i = 0}^n i \binom{n}{i} = n 2^{n - 1} \\ \sum_{i = 0}^n i^2 \binom{n}{i} = n (n + 1) 2^{n - 2} \]吸收恒等式:
\[\binom{n}{k} = \frac{n}{k} \binom{n - 1}{k - 1} \\ k \binom{n}{k} = n \binom{n - 1}{k - 1} \]三项恒等式:
\[\binom{a}{b} \binom{b}{c} = \binom{a}{c} \binom{a - c}{b - c} \]与 Fibonacci 数列联系:
\[\sum_{i = 0}^n \binom{n - i}{i} = F_{n + 1} \]与下降幂联系:
\[\frac{a^{\underline{i}}}{b^{\underline{i}}} = \frac{\binom{a}{i}}{\binom{b}{i}} = \frac{\binom {b - i}{b - a}}{\binom{b}{a}} \]组合数求和
上指标求和
朱世杰恒等式:
\[\sum_{i = k}^n \dbinom{i}{k} = \dbinom{n + 1}{k + 1} \]证明:在 \(n + 1\) 个球里拿 \(k + 1\) 个方案数为 \(\binom{n + 1}{k + 1}\) ,最后一个拿的是第 \(i + 1\) 个,对应方案数为 \(\binom{i}{k}\) 。
下指标求和
\(m\) 次询问,每次给出 \(n, k\) ,求 \(\sum_{i = 0}^k \binom{n}{i}\) 。
\(n, m, k \leq 10^5\)
考虑莫队,记 \(f(n, k) = \sum_{i = 0}^k \binom{n}{i}\) ,分类讨论得到:
\[f(n, k + 1) = f(n, k) + \binom{n}{k + 1} \\ f(n, k - 1) = f(n, k) - \binom{n}{k} \\ f(n + 1, k) = 2 f(n, k) - \binom{n}{k} \\ f(n - 1, k) = \frac{f(n, k) + \binom{n - 1}{k}}{2} \]时间复杂度 \(O(n \sqrt{n})\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 1e5 + 7;
struct Query {
int n, k, bid, id;
inline bool operator < (const Query &rhs) const {
return bid == rhs.bid ? (bid & 1 ? k > rhs.k : k < rhs.k) : n < rhs.n;
}
} qry[N];
int fac[N], inv[N], invfac[N], ans[N];
int m;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline void prework() {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int C(int n, int m) {
return m > n ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
signed main() {
prework();
m = read();
for (int i = 1; i <= m; ++i)
qry[i].n = read(), qry[i].k = read(), qry[i].bid = qry[i].n / 300, qry[i].id = i;
sort(qry + 1, qry + 1 + m);
int n = 1, k = 0, result = 1;
for (int i = 1; i <= m; ++i) {
for (; n < qry[i].n; ++n) // f(n, k) -> f(n + 1, k)
result = dec(2ll * result % Mod, C(n, k));
for (; n > qry[i].n; --n) // f(n, k) -> f(n - 1, k)
result = 1ll * inv[2] * add(result, C(n - 1, k)) % Mod;
for (; k < qry[i].k; ++k) // f(n, k) -> f(n, k + 1)
result = add(result, C(n, k + 1));
for (; k > qry[i].k; --k) // f(n, k) -> f(n, k - 1)
result = dec(result, C(n, k));
ans[qry[i].id] = result;
}
for (int i = 1; i <= m; ++i)
printf("%d\n", ans[i]);
return 0;
}
平行求和法
\[\sum_{i = 0}^k \binom{n + i}{i} = \sum_{i = 0}^k \binom{n + i}{n} = \binom{n + k + 1}{n + 1} \]二项式定理
\[(a + b)^n = \sum_{i = 0}^n \binom{n}{i} a^{n - i} b^i \]取 \(a = b = 1\) 得到:
\[\sum_{i = 0}^n \binom{n}{i} = 2^n \]取 \(a = 1, b = -1\) 得到:
\[\sum_{i = 0}^n (-1)^i \binom{n}{i} = [n = 0] \]CF660E Different Subsets For All Tuples
有一个长度为 \(n\) 的数列,每个位置上的值在 \([1, m]\) 范围内。对于所有 \(m^n\) 个序列,求每个数列中本质不同的子序列个数(包含空序列),然后求和 \(\bmod 10^9 + 7\) 。
\(n, m \leq 10^6\)
考虑统计每个子序列的出现次数(第一次),枚举子序列长度 \(i\) 与末尾位置 \(j\) ,前 \(j\) 个位置每一个不在子序列中的位置都不能与右边第一个在子序列中的位置相同,于是得到:
\[\begin{align} &\sum_{i = 1}^n \sum_{j = i}^n \binom{j - 1}{i - 1} m^{n - j + i} (m - 1)^{j - i} \\ =& \sum_{j = 0}^{n - 1} \sum_{i = 0}^j \binom{j}{i} m^{n - j + i} (m - 1)^{j - i} \\ =& \sum_{j = 0}^{n - 1} m^{n - j} \sum_{i = 0}^j \binom{j}{i} m^i (m - 1)^{j - i} \\ =& \sum_{i = 0}^{n - 1} m^{n - i} (2m - 1)^i \end{align} \]#include <bits/stdc++.h>
using namespace std;
const int Mod = 1e9 + 7;
const int N = 1e6 + 7;
int n, m;
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
signed main() {
scanf("%d%d", &n, &m);
int ans = mi(m, n);
for (int i = 0; i < n; ++i)
ans = (ans + 1ll * mi(m, n - i) * mi(m * 2 - 1, i) % Mod) % Mod;
printf("%d", ans);
return 0;
}
范德蒙德卷积
\[\sum_{i = 0}^m \binom{n}{i} \binom{m}{k - i} = \binom{n + m}{k} \]证明:
\[\begin{align} \sum_{k = 0}^{n + m} \binom{n + m}{k} x^k &= (x + 1)^{n + m} \\ &= (x + 1)^n (x + 1)^m \\ &= \sum_{r = 0}^n \binom{n}{r} x^r \sum_{s = 0}^m \binom{m}{s} x^s \\ &= \sum{k = 0}^{n + m} \sum_{r = 0}^k \binom{n}{r} \binom{m}{k - r} x^k \end{align} \]
一般化的式子:
\[\sum_{i = -r}^s \binom{n}{r + i} \binom{m}{s - i} = \binom{n + m}{r + s} \]可以得到以下推论:
\[\sum_{i = 0}^{n} \binom{n}{i}^2 = \binom{2n}{n} \]\[\sum_{i = 1}^n \dbinom{n}{i} \dbinom{n}{i - 1} = \dbinom{2n}{n - 1} \]证明:\(\sum_{i = 0}^n \binom{n}{i}^2 = \sum_{i = 0}^n \binom{n}{i} \binom{n}{n - i} = \binom{2n}{n}\) 。
\[\sum_{i = 0}^m \binom{n}{i} \binom{m}{i} = \binom{n + m}{m} \]证明:
\[\begin{align} \sum_{i = 1}^n \binom{n}{i} \binom{n}{i - 1} &= \sum_{i = 0}^{n - 1} \binom{n}{i + 1} \binom{n}{i} \\ &= \sum_{i = 0}^{i - 1} \binom{n}{n - i - 1} \binom{n}{i} \\ &= \binom{2n}{n - 1} \end{align} \]
证明:\(\sum_{i = 0}^m \binom{n}{i} \binom{m}{i} = \sum_{i = 0}^m \binom{n}{i} \binom{m}{m - i} = \binom{n + m}{m}\) 。
容斥原理
普通形式
\[|\bigcup_{i \in S} A_i| = \sum_{T \subseteq S, T \neq \emptyset} (-1)^{|T| - 1} |\bigcap_{j \in T} A_j| \]一个简单的应用是:形如给出若干组限制,求满足所有限制方案数,大部分考虑容斥。即算出钦定违反 \(k\) 条限制的方案数(这些方案之间可能会有重叠),乘以容斥系数 \((-1)^k\) 并求和。
Min-Max 容斥
最值形式:
\[\max(S) = \sum_{T \subseteq S} (-1)^{|T| + 1} \min(T) \\ \min(S) = \sum_{T \subseteq S} (-1)^{|T| + 1} \max(T) \]第 \(k\) 大/小值形式:
\[\max_k (S) = \sum_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1} \min(T) \\ \min_k(S) = \sum_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1} \max(T) \]值得注意的是,Min-Max 容斥在期望下也是成立的。一般情况下,求期望的最大/最小值并不好求,但是若最大/最小值有一个好求,那么就可以用 Min-Max 容斥求出另一个。
给出一棵树,每次等概率随机走到一个相邻点。\(q\) 次询问,每次给出一个集合 \(S\) ,求从 \(x\) (一开始就固定)出发随机游走直到点集 \(S\) 中所有点都至少经过一次的话,期望游走几步。特别地,点 \(x\) 视为一开始就被经过了一次。
\(n \leq 18\) ,\(q \leq 5000\)
求经过 \(S\) 里所有元素的期望时间,即到达 \(S\) 中最后一个点的期望步数( \(\max\) ),那么可以转化为枚举 \(S\) 的子集 \(T\) ,求到达 \(T\) 中第一个元素的期望时间( \(\min\) )。
设 \(f_{u, S}\) 表示 \(u\) 第一次走到 \(S\) 中的点的期望步数,\(d_u\) 表示 \(u\) 的度数,则:
\[f_{u, S} = \frac{f_{fa_u, S} + \sum_{v \in son(u)} f_{v, S}}{d_u} + 1 \quad (x \not \in S) \\ f_{u, S} = 0 \quad (x \in S) \]考虑将每个点的值都写作 \(f_{u, S} = k_u \times f_{fa_u, S} + b_u\) 的形式,记:
\[K_u = \sum_{v \in son(u)} k_v, B_u = \sum_{v \in son(u)} b_v \]则得到:
\[f_{u, S} = \dfrac{1}{d_u - K_u} \times f_{fa_u, S} + \dfrac{d_u + B_u}{d_u - K_u} \]即:
\[k_u = \dfrac{1}{d_u - K_u}, b_u = \dfrac{d_u + B_u}{d_u - K_u} \]答案即为 \(\sum_{T \subseteq S} (-1)^{|T| + 1} f_{r, T}\) ,不难用高维前缀和预处理后 \(O(1)\) 查询。时间复杂度 \(O(n 2^n + q)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 19;
struct Graph {
vector<int> e[N];
inline void insert(int u, int v) {
e[u].emplace_back(v);
}
} G;
int f[1 << N], g[1 << N];
int deg[N];
int n, q, r;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline int sgn(int n) {
return n & 1 ? Mod - 1 : 1;
}
struct Node {
int k, b;
inline Node(const int _b = 0) : k(0), b(_b) {}
inline Node(const int _k, const int _b) : k(_k), b(_b) {}
inline Node operator + (const Node &rhs) const {
return Node(add(k, rhs.k), add(b, rhs.b));
}
} nd[N];
void dfs(int u, int f, int state) {
Node res = 0;
for (int v : G.e[u])
if (v != f)
dfs(v, u, state), res = res + nd[v];
if (~state >> u & 1) {
nd[u].k = mi(dec(deg[u], res.k), Mod - 2);
nd[u].b = 1ll * add(deg[u], res.b) * nd[u].k % Mod;
} else
nd[u] = 0;
}
signed main() {
n = read(), q = read(), r = read() - 1;
for (int i = 1; i < n; ++i) {
int u = read() - 1, v = read() - 1;
G.insert(u, v), G.insert(v, u);
++deg[u], ++deg[v];
}
for (int i = 1; i < (1 << n); ++i)
dfs(r, -1, i), f[i] = 1ll * nd[r].b * sgn(__builtin_popcount(i) + 1) % Mod;
for (int i = 0; i < n; ++i)
for (int j = 0; j < (1 << n); ++j)
if (j >> i & 1)
f[j] = add(f[j], f[j ^ (1 << i)]);
while (q--) {
int k = read(), state = 0;
while (k--)
state |= 1 << (read() - 1);
printf("%d\n", f[state]);
}
return 0;
}
有一个 \(n\) 位二进制数 \(x\) ,初始为 \(0\) ,每次有 \(p_i\) 的概率令 \(x \gets x \operatorname{or} i\) ,求 \(x = 2^n - 1\) 的期望次数。
\(n \leq 20\)
将其转化为枚举子集 \(T\) 求 \(T\) 中元素出现一个 \(1\) 的期望,由离散随机变量的几何分布得到该值为 \(\frac{1}{p_T}\) 。求 \(p_T\) 做一遍高位前缀和就好了。
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-12;
const int N = 21;
double p[1 << N];
int n;
signed main() {
scanf("%d", &n);
int all = (1 << n) - 1;
for (int i = 0; i <= all; ++i)
scanf("%lf", p + i);
for (int i = 0; i < n; ++i)
for (int j = 0; j <= all; ++j)
if (j >> i & 1)
p[j] += p[j ^ (1 << i)];
double ans = 0;
for (int i = 1; i <= all; ++i) {
if (1 - p[all ^ i] < eps)
return puts("INF"), 0;
ans += (__builtin_parity(i) ? 1 : -1) / (1 - p[all ^ i]);
}
printf("%.6lf", ans);
return 0;
}
每秒按固定概率出现物品,求收集到 \(k\) 种物品的期望时间。
\(n \leq 1000\) ,\(n - k \leq 10\)
先令 \(k \gets n - k + 1\) ,将问题转化为 \(\max_k\) ,此时 \(k \leq 11\) 。
考虑 Min-Max 容斥:\(E(\max_k(S)) = \sum_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1} E(\min(T))\) 。设 \(f_{i, j, k}\) 表示前 \(i\) 个位置,目前选出的物品 \(\sum p = j\) ,\(\sum_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1}\) 的值。若不选 \(i\) ,则:\(f_{i, j, k} \gets f_{i - 1, j, k}\) 。否则加入 \(i\) 后 \(\sum_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1}\) 会变为 \(\sum_{T \subseteq S} (-1)^{|T| - k + 1} \binom{|T|}{k - 1}\) (即 \(|T| \to |T| + 1\) ),考虑用加法公式展开:
\[\begin{align} &\sum_{T \subseteq S} (-1)^{|T| - k + 1} \binom{|T|}{k - 1} \\ =& \sum_{T \subseteq S} (-1)^{|T| - k + 1} \left( \binom{|T| - 1}{k - 1} + \binom{|T| - 1}{k - 2} \right) \\ =& \sum_{T \subseteq S} (-1)^{|T| - (k - 1)} \binom{|T| - 1}{k - 2} - \sum_{T \subseteq S} (-1)^{|T| - k} \binom{|T| - 1}{k - 1} \end{align} \]于是可以得到:
\[f_{i, j, k} = f_{i - 1, j, k} + f_{i - 1, j - p_i, k - 1} - f_{i - 1, j - p_i, k} \]初始时 \(f_{i, 0, 0} = 1\) ,时间复杂度 \(O(nmk)\) 。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 998244353;
const int N = 1e3 + 7, M = 1e4 + 7, K = 1e1 + 7;
int p[N], inv[M], f[M][K], g[M][K];
int n, m, k;
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline void prework(int m) {
inv[0] = inv[1] = 1;
for (int i = 2; i <= m; ++i)
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
}
signed main() {
scanf("%d%d%d", &n, &k, &m);
k = n - k + 1, prework(m);
for (int i = 1; i <= n; ++i)
scanf("%d", p + i);
f[0][0] = 1;
for (int i = 1; i <= n; ++i) {
memcpy(g, f, sizeof(f));
for (int j = p[i]; j <= m; ++j)
for (int l = 1; l <= k; ++l)
f[j][l] = add(f[j][l], dec(g[j - p[i]][l - 1], g[j - p[i]][l]));
}
int ans = 0;
for (int i = 1; i <= m; ++i)
ans = add(ans, 1ll * f[i][k] * m % Mod * inv[i] % Mod);
printf("%d", ans);
return 0;
}
反演相关
反演:两个函数(数列)之间的双向(求和)关系。
如:已知 \(F = A \times G\) ,其中 \(A\) 为关系矩阵,即 \(F_n = \sum_{i = 0} A_{n, i} G_i\) ,则可以得到 \(G = A^{-1} \times F\) 。事实上有定理:两个互为反演的关系矩阵互逆。
二项式反演
形式一
\[f(n) = \sum_{i = 0}^n (-1)^i \binom{n}{i} g(i) \iff g(n) = \sum_{i = 0}^n (-1)^i \binom{n}{i} f(i) \]非常对称的形式。
形式二
\[f(n) = \sum_{i = n}^m (-1)^i \binom{i}{n} g(i) \iff g(n) = \sum_{i = n}^m (-1)^i \binom{i}{n} f(i) \]上下指标互换,同样非常对称的形式。
形式三
\[f(n) = \sum_{i = 0}^n \dbinom{n}{i} g(i) \iff g(n) = \sum_{i = 0}^n (-1)^{n - i} \dbinom{n}{i} f(i) \]记 \(f(n)\) 表示从 \(n\) 个不同元素中钦定若干个元素满足条件的方案数,\(g(n)\) 表示 \(n\) 个不同元素均满足条件的方案数。
这个形式可以实现 \(f, g\) 的互推,事实上右边可以理解为钦定 \(n - i\) 个元素不满足条件。
形式四
\[f(n) = \sum_{i = n}^m \dbinom{i}{n} g(i) \iff g(n) = \sum_{i = n}^m (-1)^{i - n} \dbinom{i}{n} f(i) \]形式三上下指标互换的推论。把组合数拆开看看:
\[g_n = \sum_{i = n}^m (-1)^{i - n} \frac{i!}{n! (n - i)!} f_i \\ g_n = \frac{1}{n!} \sum_{i = n}^m \frac{(-1)^{i - n}}{(i - n)!} \times i! f_i \]用 NTT 计算差卷积即可做到 \(O(n \log n)\) 。计算差卷积只要先 reverse
一下,最后再 reverse
回来即可。
有一个长度为 \(n\) 的序列,可以给每个位置染上 \([1, m]\) 的颜色。若恰好出现 \(S\) 次的颜色有 \(k\) 种,则会获得 \(w_k\) 的价值。求所有染色方案的价值和。
\(n \leq 10^7\) ,\(m \leq 10^5\) ,\(S \leq 150\)
不难发现当 \(k > \min(m, \lfloor \frac{n}{s} \rfloor)\) 时 \(w_k\) 不会被统计,下令 \(n = \min(m, \lfloor \frac{n}{s} \rfloor)\) 。
考虑容斥,钦定 \(k\) 种颜色染 \(S\) 次,则:
\[f(k) = \binom{m}{k} \times \frac{n!}{(S!)^k (n - kS)!} \times (n - kS)^{m - k} \]不难发现这个会算重,设恰好出现 \(S\) 次的颜色有 \(k\) 种的方案数为 \(g(k)\) ,则不难得到:
\[f(k) = \sum_{i = k}^n \binom{i}{k} g_i \]二项式反演得到:
\[g(k) = \sum_{i = k}^n (-1)^{i - k} \binom{i}{k} f_i \]NTT 做差卷积即可。
#include <bits/stdc++.h>
using namespace std;
const int Mod = 1004535809;
const int N = 1e7 + 7, M = 1e5 + 7;
int fac[N], inv[N], invfac[N];
int a[M], f[M], g[M], h[M];
int n, m, s;
template <class T = int>
inline T read() {
char c = getchar();
bool sign = (c == '-');
while (c < '0' || c > '9')
c = getchar(), sign |= (c == '-');
T x = 0;
while ('0' <= c && c <= '9')
x = (x << 1) + (x << 3) + (c & 15), c = getchar();
return sign ? (~x + 1) : x;
}
inline int add(int x, int y) {
x += y;
if (x >= Mod)
x -= Mod;
return x;
}
inline int dec(int x, int y) {
x -= y;
if (x < 0)
x += Mod;
return x;
}
inline int mi(int a, int b) {
int res = 1;
for (; b; b >>= 1, a = 1ll * a * a % Mod)
if (b & 1)
res = 1ll * res * a % Mod;
return res;
}
inline int sgn(int n) {
return n & 1 ? Mod - 1 : 1;
}
inline void prework() {
fac[0] = fac[1] = 1;
inv[0] = inv[1] = 1;
invfac[0] = invfac[1] = 1;
for (int i = 2; i < N; ++i) {
fac[i] = 1ll * fac[i - 1] * i % Mod;
inv[i] = 1ll * (Mod - Mod / i) * inv[Mod % i] % Mod;
invfac[i] = 1ll * invfac[i - 1] * inv[i] % Mod;
}
}
inline int C(int n, int m) {
return m > n ? 0 : 1ll * fac[n] * invfac[m] % Mod * invfac[n - m] % Mod;
}
namespace Poly {
#define cpy(f, g, n) memcpy(f, g, sizeof(int) * (n))
#define clr(f, n) memset(f, 0, sizeof(int) * (n))
const int rt = 3, invrt = 334845270;
const int S = 2e6 + 7;
int rev[S];
inline void calrev(int n) {
for (int i = 1; i < n; ++i)
rev[i] = (rev[i >> 1] >> 1) | (i & 1 ? n >> 1 : 0);
}
inline int calc(int n) {
int len = 1;
while (len <= n)
len <<= 1;
return calrev(len), len;
}
inline void NTT(int *f, int n, int op) {
for (int i = 0; i < n; ++i)
if (i < rev[i])
swap(f[i], f[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
int tG = mi(op == 1 ? rt : invrt, (Mod - 1) / (k << 1));
for (int i = 0; i < n; i += k << 1) {
int buf = 1;
for (int j = 0; j < k; ++j) {
int fl = f[i + j], fr = 1ll * buf * f[i + j + k] % Mod;
f[i + j] = add(fl, fr), f[i + j + k] = dec(fl, fr);
buf = 1ll * buf * tG % Mod;
}
}
}
if (op == -1) {
int invn = mi(n, Mod - 2);
for (int i = 0; i < n; ++i)
f[i] = 1ll * f[i] * invn % Mod;
}
}
inline void Times(int *f, int *g, int n) {
NTT(f, n, 1), NTT(g, n, 1);
for (int i = 0; i < n; ++i)
f[i] = 1ll * f[i] * g[i] % Mod;
NTT(f, n, -1);
}
inline void Mul(int *f, int n, int *g, int m, int *res) {
static int a[S], b[S];
int len = calc(n + m - 1);
cpy(a, f, n), clr(a + n, len - n);
cpy(b, g, m), clr(b + m, len - m);
Times(a, b, len), cpy(res, a, n + m - 1);
}
#undef cpy
#undef clr
} // namespace Poly
signed main() {
prework();
n = read(), m = read(), s = read();
for (int i = 0; i <= m; ++i)
a[i] = read();
int lim = min(m, n / s);
for (int i = 0; i <= lim; ++i) {
f[i] = 1ll * C(m, i) * fac[n] % Mod * mi(invfac[s], i) % Mod *
invfac[n - s * i] % Mod * mi(m - i, n - s * i) % Mod * fac[i] % Mod;
h[i] = 1ll * sgn(i) * invfac[i] % Mod;
}
reverse(f, f + lim + 1);
Poly::Mul(f, lim + 1, h, lim + 1, g);
reverse(g, g + lim + 1);
int ans = 0;
for (int i = 0; i <= lim; ++i)
ans = add(ans, 1ll * g[i] * invfac[i] % Mod * a[i] % Mod);
printf("%d", ans);
return 0;
}
多元形式
\[f(n_1, n_2, \cdots, n_m) = \sum_{k_i = 0}^{n_i} \prod_{i = 1}^m \binom{n_i}{k_i} g(k_1, k_2, \cdots, k_m) \\ g(n_1, n_2, \cdots, n_m) = \sum_{k_i = 0}^{n_i} \prod_{i = 1}^m (-1)^{n_i - k_i} \binom{n_i}{k_i} f(k_1, k_2, \cdots, k_m) \]子集反演
\[f(S) = \sum_{T \subseteq S} g(T) \iff g(S) = \sum_{T \subseteq S} (-1)^{|S| - |T|} f(T) \\ f(S) = \sum_{S \subseteq T} g(T) \iff g(S) = \sum_{S \subseteq T} (-1)^{|T| - |S|} f(T) \]格路计数
无限制
记 \([a, b, c, d]\) 表示 \((a, b)\) 走到 \((c, d)\) 的路径数,则有:
\[[a, b, c, d] = \dbinom{c - a}{\frac{c - a + d - b}{2}} \]证明:考虑有 \(x\) 步是往右上走,有 \(y\) 步是往右下走。则:
\[\begin{cases} a + x + y = c \\ b + x - y = d \end{cases} \]解得:
\[\begin{cases} x = \dfrac{c - a + d - b}{2} \\ y = \dfrac{c - a + b - d}{2} \end{cases} \]考虑到总共 \(c - a\) 步,其中放 \(x\) 个向右上走的,则:
\[[a, b, c, d] = \dbinom{c - a}{\frac{c - a + d - b}{2}} \]
inline int walk(int a, int b, int c, int d) {
if (((c - a + d - b) & 1) || d - b > c - a || b - d > c - a)
return 0;
else
return C(c - a, (c - a + d - b) / 2);
}
一条直线
相交
记 \(<a, b, c, d, k>\) 表示 \((a, b)\) 走到 \((c, d)\) 且与直线 \(y = k\) 相交的路径数,则有:
\[<a, b, c, d, k> = [a, b, c, 2k - d] \]证明:考虑所有 \((a, b) \to (c, 2k - d)\) 的路径,其必然与 \(y = k\) 有交点,取第一个交点出来,把之后的路线关于 \(y = k\) 对称一下就是到 \((c, d)\) 的一个与 \(y = k\) 相交的路线。即 \((a, b) \to (c, d)\) 与 \((a, b) \to (c, 2k - d)\) 的合法路径构成双射(与 \(y = k\) 相交)。
不相交
记 \(\{ a, b, c, d, k \}\) 表示 \((a, b)\) 走到 \((c, d)\) 且与直线 \(y = k\) 不相交的路径数,则有:
\[\{ a, b, c, d, k \} = [a, b, c, d] - [a, b, c, 2k - d] \]两条直线
记:
- \(f(a, b, k_1, k_2)\) 表示 \((0, 0)\) 走到 \((a, b)\) 且与直线 \(y = k_1\) 与 \(y = k_2\) 都不相交的路径数。
- \(g(a, b, k_1, k_2)\) 表示 \((0, 0)\) 走到 \((a, b)\) 且与直线 \(y = k_1\) 相交而直线与 \(y = k_2\) 不相交的路径数。
则有:
\[f(a, b, k_1, k_2) = [0, 0, a, b] - <0, 0, a, b, k_2> - g(a, b, k_1, k_2) \\ g(a, b, k_1, k_2) = f(a, 2k_1 - b, 2k_1 - k_2, k_2) + g(a, b, 2k_1 - k_2, k_2) \]证明:\(f\) 比较显然,考虑证明 \(g\) 。先让路径强制触碰 \(y = k_1\) ,将路径的第一个交点以后的部分对称。注意到对称点之后的红线碰到 \(y = k_2\) 是可以的,考虑加回来。它一定会先与直线 \(y = k_1\) 相交,然后跟 \(y = k_2\) 相交。那么把 \((0, 0)\) 也对称下来到 \((0, 2k_1)\) ,漏算的方案即为一条从 \((0, 2k_1)\) 到 \((a, 2k_1 - b)\) 且不能经过 \(y = k_3\) 而必须经过 \(y = k_2\) 的路径数。
然后为什么这个把问题规模缩小了呢?
考虑到两条直线间的距离每次都会扩大两倍,而直线太远的时候我们就可以不去考虑直线的影响,并且无法碰到 \(y = k_1\) 时就可以直接停止递归。
int f(int a, int b, int k1, int k2) { //不能碰 y = k1 和 y = k2
if (a < -b || a < b)
return 0;
else
return dec(dec(walk(0, 0, a, b), walk(0, 0, a, 2 * k2 - b)), g(a, b, k1, k2));
}
int g(int a, int b, int k1, int k2) { //必须碰 y = k1 而不能碰 y = k2
if (a < -b || a < b)
return 0;
else if (a < 2 * k1 - b || a < b - 2 * k1)
return 0;
else
return add(f(a, 2 * k1 - b, 2 * k1 - k2, k2), g(a, b, 2 * k1 - k2, k2));
}
当直线间的距离越大,计算越快,而距离太小的时候一般可以直接DP。复杂度期望是线性的。
常见数列
Catalan 数
以下看似毫不相关的问题均属于 Catalan 数列:
- \(n\) 个节点构成的无标号、区分左右儿子的二叉树数量为 \(Cat_n\) 。
- \(n\) 个节点构成的无标号、区分儿子的有根树数量为 \(Cat_{n - 1}\) 。
- \(n\) 个左括号与 \(n\) 个右括号组成的合法序列有 \(Cat_n\) 种。
- \(n\) 个元素按照大小进栈,合法的出栈序列有 \(Cat_n\) 种。
- \(n\) 边凸多边形的三角剖分方案数为 \(Cat_{n - 2}\) 。
- 从 \((0,0)\) 出发,每次沿正方向走,到达 \((n,n)\) 且不接触直线 \(y=x\) 的路径数量为 \(Cat_n\) 。
- 以圆上的 \(2n\) 个点为端点,连互不相交的 \(n\) 条弦的方案数为 \(Cat_n\) 。
- 将 \(1 \sim 2n\) 中的数不重不漏地填入 \(2 \times n\) 的矩阵,每个数字大于其左边和上面数字的方案数 \(Cat_n\) 。
Catalan 数列的前几项为:
\(Cat_0\) | \(Cat_1\) | \(Cat_2\) | \(Cat_3\) | \(Cat_4\) | \(Cat_5\) | \(\cdots\) |
---|---|---|---|---|---|---|
\(1\) | \(1\) | \(2\) | \(5\) | \(14\) | \(42\) | \(\cdots\) |
常见求法:
\[\begin{align} Cat_n &= \frac{\binom{2n}{n}}{n + 1} & (n > 2) \\ Cat_n &= \binom{2n}{n} - \binom{2n}{n - 1} \\ Cat_n &= \frac{(4n - 2)Cat_{n - 1}}{n + 1} \\ Cat_n &= \sum_{i = 1}^n Cat_{i - 1} \times Cat_{n - i} & (n > 2) \end{align} \]Bell 数
Bell 数列的前几项为:
\(B_0\) | \(B_1\) | \(B_2\) | \(B_3\) | \(B_4\) | \(B_5\) | \(\cdots\) |
---|---|---|---|---|---|---|
\(1\) | \(1\) | \(2\) | \(5\) | \(15\) | \(52\) | \(\cdots\) |
常见求法:
\[\begin{align} B_n &= \sum_{k = 0}^n {n \brace k} \\ B_{n + 1} &= \sum_{k = 0}^n \dbinom{n}{k} B_k \end{align} \]Fibonacci 数列
Fibonacci 数列的前几项为:
\(F_0\) | \(F_1\) | \(F_2\) | \(F_3\) | \(F_4\) | \(F_5\) | \(\cdots\) |
---|---|---|---|---|---|---|
\(0\) | \(1\) | \(1\) | \(2\) | \(3\) | \(5\) | \(\cdots\) |
常见求法:
\[F_i = \begin{cases} 1 & (i \leq 2) \\ F_{i - 1} + F_{i - 2} & (i \geq 3) \end{cases} \\ F_i = \frac{1}{\sqrt{5}} \left( (\frac{1 + \sqrt{5}}{2})^n - (\frac{1 - \sqrt{5}}{2})^n \right) \\ \begin{cases} F_{2n} = F_n (2 F_{n + 1} - F_n) \\ F_{2n + 1} = F_{n + 1}^2 + F_n^2 \end{cases} \]常用结论:
-
\(F_{n - 1} F_{n + 1} - F_n^2 = (-1)^n\) 。
-
\(F_{n + m} = F_{n} F_{m + 1} + F_{n - 1} F_m\) 。
- 特殊情况:\(F_{2n} = F_n(F_{n + 1} + F_{n - 1})\) 。
-
\(n \mid m \iff F_n \mid F_m\) 。
-
\(\gcd(F_n, F_m) = F_{\gcd(n, m)}\) 。
-
齐肯多夫定理:任何正整数都可以表示成若干个不连续的斐波那契数(不包括第一个斐波那契数)之和。
模 \(m\) 意义下 Fibonacci 数列的最小正周期被称为皮萨诺周期,相关结论:
- 皮萨诺周期总是不超过 \(6m\) (\(m = 2 \times 5^k\) 时取等)。
- Fibonacci 数列 \(\bmod 2\) 的最小正周期为 \(3\) 。
- Fibonacci 数列 \(\bmod 5\) 的最小正周期为 \(20\) 。
- Fibonacci 数列 \(\bmod p\) ( \(p\) 是素数且 \(p \equiv \pm 1 \pmod{5}\) )的最小正周期是 \(p - 1\) 的因数。
- Fibonacci 数列 \(\bmod p\) ( \(p\) 是素数且 \(p \equiv \pm 2 \pmod{5}\) )的最小正周期是 \(2p + 2\) 的因数。