P6620组合数问题如何转化为?

摘要:P6620 [省选联考 2020 A 卷] 组合数问题 大意 求 [left(sum_{k=0}^{n}f(k)times x^ktimes binom{n}{k}right)bmod p ]的值。其中 (n), (
P6620 [省选联考 2020 A 卷] 组合数问题 大意 求 \[\left(\sum_{k=0}^{n}f(k)\times x^k\times \binom{n}{k}\right)\bmod p \] 的值。其中 \(n\), \(x\), \(p\) 为给定的整数,\(f(k)\) 为给定的一个 \(m\) 次多项式 \(f(k) = a_0 + a_1k + a_2k^2 + \cdots + a_mk^m\)。\(\binom{n}{k}\) 为组合数,其值为 \(\binom{n}{k} = \frac{n!}{k!(n-k)!}\)。 思路 好题! 我们先考虑这个式子的暴力处理方式,我们发现是外层枚举 \(k\),内层枚举 \(f(k)\) 的 \(k\),这样是 \(\mathcal{O}(nm)\) 的,但是我们发现 \(1 \le n \le 10 ^ 9\),但是我们的 \(m\) 是 \(1 \le m \le 10 ^ 3\) 的,我们首先考虑的是把复杂度中的 \(n\) 化为用 \(m\) 的复杂度能解决的事情。 我们发现原始的式子中,只有 \(f(k)\) 是需要我们额外处理的,所以我们先看这个东西,我们考虑化简,这个时候需要用到 \(k\) 次下降幂的东西,众所周知 $x ^ k = x \times x \times \ldots $,那么我们的 \(x ^ {\underline{k}} = x(x - 1) \ldots (x - k + 1)\),为什么我们需要知道这个东西呢?因为我们发现 \(f(k)\) 中的 \(k ^ j\) 无法很好的运算,我们考虑幂的转化,即利用第二类斯特林数将 \(x ^ k \to x ^ {\underline{k}}\),如何做呢?我们有这样的恒等式:\(k ^ i = \sum_{j = 0}^{i} {i \brace j} k ^ {\underline{j}}\)。 那这个时候就有人要问了,主播主播什么是第二类斯特林数?我们考虑组合意义,是这样的: \({i \brace j}\) 表示将 \(i\) 个互不相同的球放到 \(j\) 个相同的盒子里(每个盒子都不为空)的方案数。那递推式就是:\({i \brace j} = {i - 1 \brace j - 1} + j \times {i - 1 \brace j}\),那这个式子我们可以用组合的方式去理解一下,就是左边的 \({i - 1 \brace j - 1}\) 就是开一个新的盒子,而右边的 \(j \times {i - 1 \brace j}\) 就是用旧的盒子。 好,回到正题,我们考虑将原始的 \(f(k)\) 优化代换一下,用 \(k ^ {\underline{j}}\) 替代 \(k ^ j\),这样的话前面会产生一个新的系数,我们将这个系数记为 \(b_j\),那么 \(b_j = \sum_{i = 1}^{m} a_i \times {i \brace j}\)。那么我们将这个 \(b_j\) 带入原式,那么我们的式子就变成了这样一坨: \[\sum_{k = 0} ^ {n} (\sum_{j = 0} ^ {m} b_j k ^ {\underline{j}}) x ^ k \binom{n}{k} \] 接下来,我们先交换 \(k\) 与 \(j\) 的枚举顺序: \[ \sum_{j = 0} ^ {m} b_j (\sum_{k = 0} ^ {n} k ^ {\underline{j}} x ^ k \binom{n}{k}) \] 接下来我我们利用吸收公式的 \(k\) 次下降幂的形式: \[k ^ {\underline{j} }\binom{n}{k} = n ^ {\underline{j}}\binom{n - j}{k - j} \] 将其带入,得到: \[\sum_{j = 0} ^ {m} b_j (\sum_{k = j}^{n} n ^ {\underline{j}} \binom{n - j}{k - j} x ^ k) \] 我们提取一下系数: \[\sum_{j = 0} ^ {m} b_j n ^ {\underline{j}} \cdot x ^ j \sum_{k = j} ^ {n} \binom{n - j}{k - j} x ^ {k - j} \] 不难注意到右边是二项式定理的形式,故可以继续化简: \[\sum_{j = 0} ^ {m} b_j n ^ {\underline{j}} \cdot x ^ j (x + 1) ^ {n - j} \] 到这边,我们的任务就完成了,\(b_j\) 可以提前 \(\mathcal{O}(m ^ 2)\) 预处理出来,\(n ^ {\underline{j}}\) 可以随着递推求,\(x^j\) 也可以随着递推求,至于 \((x + 1)^{n - j}\) 就可以直接快速幂求了,总时间复杂度就是 \(\mathcal{O}(m ^ 2)\)。 代码 #include<bits/stdc++.h> using namespace std; const int MAXM = 1005; #define ll long long int n, x, p, m; ll a[MAXM], b[MAXM]; ll S[MAXM][MAXM]; ll qpow(ll a, ll b){ ll res = 1; a %= p; while(b){ if(b & 1) res = res * a % p; a = a * a % p; b >>= 1; } return res; } int main(){ srand(time(NULL)); cout << rand() % 40; cin.tie(0) -> ios::sync_with_stdio(0); cin >> n >> x >> p >> m; for(int i = 0;i <= m;i ++) cin >> a[i]; S[0][0] = 1; for(int i = 1;i <= m;i ++){ for(int j = 1;j <= i;j ++){ S[i][j] = (S[i - 1][j - 1] + j * S[i - 1][j]) % p; } } for(int j = 0;j <= m;j ++){ for(int i = 0;i <= m;i ++){ (b[j] += ((a[i] * S[i][j]) % p)) %= p; } } ll mi = 1, ans = 0, xi = 1; for(int i = 0;i <= m;i ++){ ll del = b[i]; (del *= mi) %= p; (del *= xi) %= p; (del *= qpow(x + 1, n - i)) %= p; (ans += del) %= p; // (ans += (b[i] * mi * qpow(x, i) * qpow(x + 1, n - i))) %= p; (mi *= (n - i)) %= p; (xi *= x) %= p; } cout << (ans + p) % p << '\n'; return 0; }