数学杂项
本文多为参考《具体数学》一书,若有其他参考资料将额外列出。
递归问题
一般步骤
- 研究小的情形——这有利于我们洞察该问题
- 对有意义的量求出数学表达式并给出证明(如求出递归式)
- 对数学表达式求出封闭形式并给出证明(一般是对递归式求出递归解)
和式处理
和式运算律
- 分配律 $\sum_{k \in K}ca_k = c \sum_{k \in K}a_k$
- 结合律 $\sum_{k \in K} (a_k + b_k) = \sum_{k \in K} a_k + \sum_{k \in K} b_k$
- 交换律 $\sum_{k \in K}a_k = \sum_{p(k)\in K} a_{p(k)}$
其他
自然数立方和公式
证明(是《Proofs that Really Count》一书第8章Number Theory中的内容)
运用数学归纳法。
当 $n = 1$ 时,$1^3 = \left ( \frac{1 \times 2}{2} \right ) ^ 2$ ,成立。
假设当 $n = k$ 时, $\sum_{i = 1}^k i^3 = \left ( \frac{k(k+1)}{2} \right )^2$ 成立,
那么当 $n = k + 1$ 时,
得证。
任何一个十进制整数模 9 同余于它各数位上数字的和
证明(不严谨) :
把这个十进制数拆成各数位乘上十的幂次的和的形式。
由于十的各幂次模 $9$ 都为 $1$,且模运算对整数加法和乘法具有结合律,发现每个十的幂都可以化为 $1$ 消掉。
然后就变成了各数位的和。
二进制集合操作
在状压DP中很常用,算是必备技能。
lyd蓝书上似乎有比较全面的介绍,但是手里没蓝书(
所以只能借鉴 OI Wiki
交并补和取出后几位这样的操作大致不必介绍了。
最简单的遍历集合是遍历全集,简单来讲 :
1 | for(int i = 1;i <= (1 << n);i++); |
这样得到的 $i$ 就是所有子集。
而在很多情况下,我们希望遍历的是某个给定集合的子集,那么此时再使用这样的方法并判断 $i$是否被已知集合包含则会导致许多不必要的运算。
于是我们可以这样做 :
1 | for(int s = m;s;s = (s - 1) & m); |
显然这份代码不会遍历空集,若要考虑空集应当加上特判。
考虑这份代码的意义,每次的减一操作实际上是把 lowbit
的位置变成 $0$,然后将所有更低位变成 $1$,然后与原集合取交,避免了多余元素出现。于是我们忽略掉原集合中为 $0$ 的位置会发现:这样做实际上就像是枚举集合中有几个元素然后枚举位置的过程,于是感性理解之下我们可以明白这个做法的正确性。
以上两种做法的复杂度是 $O(2^n)$ 量级,更严谨地说,第二种方法的复杂度应当是 $O(2^{popcount(m)})$。
结合以上两种方法,我们可以遍历全集中每个元素的子集 :
1 | for(int m = 0;m < (1 << n);m++) |
其中 $n$ 是全集中元素的个数。
这个做法的复杂度是 $O(3^n)$ ,证明来自oi-wiki :
考虑某一个位置的方案有几种 :
- 该元素不在两个集合中
- 该元素在大集合中,但不在小集合中
- 该元素在这两个集合中
于是枚举的过程就是组合所有位置的这三种情况,复杂度为 $O(3^n)$。