计算分拆数的一种方法
原始问题
给定
例如
而
为了方便,这里将与数字相关的基本运算的时间复杂度均视为常数时间。
基本思路
主要还是利用双计数的套路,对于
对于
将
$n$ 拆分为不同的部分的方案数等于将$n$ 拆分为各部分都是奇数的方案数。
证明
由于每个数
- 给定一个每个部分不同的拆分方案,构造一个新的拆分方案:对于每个数
$n = 2^k \cdot t$ ,在新方案中加入$2^k$ 个$t$ 。易知新方案中的每个部分都是奇数。 - 从上面给出的新方案还原原来的方案:统计每个奇数
$t$ 的出现次数$c$ ,并将其表示为二进制,即$c = c_0c_1...{c_p}_{(2)}$ 。对于$0 \leqslant k \leqslant p$ ,如果$c_k = 1$ ,那么表示原方案中有一个数为$2^k \cdot t$ 。
举个例子,如
证明
根据几何级数,我们知道:
注意到分子部分总是偶数次幂。因此设
即
现在回到原来的问题,由于只是要求每个部分都是奇数,所以可以再一次运用双计数:
现在令
令
变成了一个卷积的形式。
实现
下面的程序实现了计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | #include <cstring> #include <algorithm> #include <iostream> using namespace std; #define NMAX 400000 #define MOD 998244353 #define G 3 typedef long long i64; inline i64 qpow(i64 a, int b) { i64 r = 1; for (; b; a = a * a % MOD, b >>= 1) if (b & 1) r = r * a % MOD; return r; } inline i64 inv(i64 x) { return qpow(x, MOD - 2); } inline void add(i64 &a, i64 b) { a += b; if (a >= MOD) a -= MOD; else if (a < 0) a += MOD; } inline int nxtp(int n) { int r = 1; for (; r < n; r <<= 1); return r; } void fft(i64 *a, int n, bool in = false) { static i64 b[NMAX + 10]; memcpy(b, a, sizeof(i64) * n); for (int i = 0, k = 0, j; i < n; k |= j, i++) { a[i] = b[k]; for (j = n >> 1; k & j; k ^= j, j >>= 1); } for (int s = 2; s <= n; s <<= 1) { int l = s >> 1; i64 wn = qpow(G, in ? MOD - (MOD - 1) / s - 1 : (MOD - 1) / s); for (int i = 0; i < n; i += s) { i64 w = 1; for (int j = i; j < i + l; j++) { i64 t = a[j + l]; a[j + l] = (a[j] - w * t) % MOD; a[j] = (a[j] + w * t) % MOD; w = w * wn % MOD; } } } if (in) { i64 d = inv(n); for (int i = 0; i < n; i++) { a[i] = a[i] * d % MOD; } } } static int n; static i64 q[NMAX + 10], c[NMAX + 10]; void initialize() { ios::sync_with_stdio(false); cin >> n; q[0] = 1; for (int i = 1; i <= n; i += 2) { for (int j = i; j <= n; j += i) { add(c[j], i); } } } static i64 a[NMAX + 10], b[NMAX + 10]; void solve(int l, int r) { if (l == r) { if (l) q[l] = q[l] * inv(l) % MOD; return; } int m = (l + r) >> 1; solve(l, m); int alen = m - l + 1, blen = r - l + 1; int len = nxtp(blen - 1); memset(a, 0, sizeof(i64) * len); memset(b, 0, sizeof(i64) * len); memcpy(a, q + l, sizeof(i64) * alen); memcpy(b, c, sizeof(i64) * blen); fft(a, len); fft(b, len); for (int i = 0; i < len; i++) { a[i] = a[i] * b[i] % MOD; } fft(a, len, true); for (int i = alen; i < blen; i++) { add(q[m + i - alen + 1], a[i]); } solve(m + 1, r); } int main() { initialize(); solve(0, n); for (int i = 0; i <= n; i++) { cout << q[i] << " "; } cout << "\n"; return 0; } |
-
这里也有下面两个证明:https://math.stackexchange.com/q/54976 ↩