ACM - 组合数学完全总结(知识点 + 模板)【用 LaTeX 重写前作者文章中所有公式】

一 排列

1. 不可重排列数:

A n r = n ( n − 1 ) ( n − 2 ) ⋯ ( n − r + 1 ) A_{n}^{r}=n(n-1)(n-2) \cdots (n-r+1) Anr=n(n1)(n2)(nr+1)

n n n r r r 都是整数,且 0 ≤ r ≤ n 0 \le r \le n 0rn,有
A n r = n ! ( n − r ) ! A_n^r=\dfrac{n!}{(n-r)!} Anr=(nr)!n!

2. 可重排列数:

n n n 个物品中可重复的取 k k k 个的排列数为: n k n^k nk

3. 圆排列:

n n n 个不同物品中取 m m m 个的圆排列: P n m m = n ! ( n − m ) ! × m , ( 1 ≤ m ≤ n ) \dfrac{P_n^m}{m}=\dfrac{n!}{(n-m)! \times m},(1 \le m \le n) mPnm=(nm)!×mn!,(1mn)

4. 不尽相异元素全排列:

如果 n n n 个元素里,有 p p p 个元素相同,又有 q q q 个元素相同,…,又有 r r r 个元素相同 ( p + q + ⋯ + r ≤ n ) , (p+q+ \cdots +r \le n), (p+q++rn), 则它的所有排列总数为:
n ! p ! q ! ⋯ r ! \frac{n!}{p!q! \cdots r!} p!q!r!n!

5. 多重集的排列
  1. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,从无限多重集 { ∞ ∗ a 1 , ∞ ∗ a 2 , . . . . . , ∞ ∗ a n } \{∞*a_1,∞*a_2,.....,∞*a_n\} {a1,a2,.....,an} 中取 r r r 个元素的排列数为 n r n^r nr
  2. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,从有限多重集 { k 1 ∗ a 1 , k 2 ∗ a 2 , . . . . . , k n ∗ a n } \{k_1*a_1,k_2*a_2,.....,k_n*a_n\} {k1a1,k2a2,.....,knan} 中取 r r r 个元素的排列数为 n r n^r nr ,各 k k k ≥ r \geq r r
  3. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,有限多重集 { k 1 ∗ a 1 , k 2 ∗ a 2 , . . . . . , k n ∗ a n } \{{k_1*a_1,k_2*a_2,.....,k_n*a_n}\} {k1a1,k2a2,.....,knan} 中元素的全排列为 ( k 1 + k 2 + . . . + k n ) ! k 1 ! k 2 ! ⋯ k n ! \dfrac{(k_1+k_2+...+k_n)! }{k_1! k_2! \cdots k_n!} k1!k2!kn!(k1+k2+...+kn)!
  4. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,从有限多重集 { k 1 ∗ a 1 , k 2 ∗ a 2 , . . . . . , k n ∗ a n } \{k_1*a_1,k_2*a_2,.....,k_n*a_n\} {k1a1,k2a2,.....,knan} 中取 r r r 个元素,至少存在一个 k i < r k_i< r ki<r 时,排列数为 r r ! k 1 ! k 2 ! ⋯ k n ! r \dfrac { r ! } {k_1 ! k_2 ! \cdots k_n !} rk1!k2!kn!r!,即指数型母函数 G ( x ) = 1 + x 1 ! + x 2 2 ! + . . . + x k i k i ! G(x)=1+\dfrac{x}{1!}+\dfrac{x^2}{2!}+...+\dfrac{x^{k_i}}{k_i!} G(x)=1+1!x+2!x2+...+ki!xki x r x^r xr 系数

二 组合

1. 不可重组合数:

C n r = n ! r ! ( n − r ) ! C_n^r=\frac{n!}{r!(n-r)!} Cnr=r!(nr)!n!

2. 可重组合数:

H n r = C n + r − 1 r = ( n + r − 1 ) ! r ! ( n − 1 ) ! H_n^r=C_{n+r-1}^r=\frac{(n+r-1)!}{r!(n-1)!} Hnr=Cn+r1r=r!(n1)!(n+r1)!

3. 不相邻组合

A = { 1 , 2 , 3 , … , n } A=\{1,2,3, \dots,n\} A={1,2,3,,n}中取 m m m 个不相邻组合,其组合数为 C n − m + 1 m C_{n-m+1}^m Cnm+1m

4. 多重集的组合
  1. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,从无限多重集 { ∞ ∗ a 1 , ∞ ∗ a 2 , . . . . . , ∞ ∗ a n } \{∞*a_1,∞*a_2,.....,∞*a_n\} {a1,a2,.....,an} 中取 r r r 个元素的组合数为 C n + r − 1 r C_{n+r-1}^r Cn+r1r
  2. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,从有限多重集 { k 1 ∗ a 1 , k 2 ∗ a 2 , . . . . . , k n ∗ a n } \{k_1*a_1,k_2*a_2,.....,k_n*a_n\} {k1a1,k2a2,.....,knan} , 各 k k k 均大于等于 r r r,从中取 r r r 个元素的组合数为 C n + r − 1 r C_{n+r-1}^r Cn+r1r
  3. 设元素 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 互不相同,从有限多重集 { k 1 ∗ a 1 , k 2 ∗ a 2 , . . . . . , k n ∗ a n } \{k_1*a_1,k_2*a_2,.....,k_n*a_n\} {k1a1,k2a2,.....,knan} 中取 r r r 个元素,至少存在一个 k i < r k_i < r ki<r
    令母函数 G ( x ) = 1 + x + x 2 + . . . + x k i , i = 1 , 2 , ⋯   , n G(x)=1+x+x^2 +...+x^{k_i} ,i=1,2,\cdots,n G(x)=1+x+x2+...+xki,i=1,2,,n G ( x ) G (x) G(x) x r x^r xr 的系数即为所求
5. 常用组合数公式
  1. C n m = C n − 1 m + C n − 1 m − 1 C_n^m = C_{n − 1}^m + C_{n − 1}^{m − 1} Cnm=Cn1m+Cn1m1

  2. C n m = C n n − m C_n^m = C_n^{n − m} Cnm=Cnnm

  3. C n m + 1 = n − m ( m + 1 ) C n m C_n^{m + 1}= \frac{n − m}{( m + 1 ) C_n^m} Cnm+1=(m+1)Cnmnm

6. 组合数取模
typedef long long LL;
const LL maxn(1000005), mod(1e9 + 7);
LL Jc[maxn];
void calJc()    //求maxn以内的数的阶乘
{
    Jc[0] = Jc[1] = 1;
    for(LL i = 2; i < maxn; i++)
        Jc[i] = Jc[i - 1] * i % mod;
}
/*
//拓展欧几里得算法求逆元
void exgcd(LL a, LL b, LL &x, LL &y)    //拓展欧几里得算法
{
    if(!b) x = 1, y = 0;
    else
    {
        exgcd(b, a % b, y, x);
        y -= x * (a / b);
    }
}
LL niYuan(LL a, LL b)   //求a对b取模的逆元
{
    LL x, y;
    exgcd(a, b, x, y);
    return (x + b) % b;
}
*/
//费马小定理求逆元
LL pow(LL a, LL n, LL p)    //快速幂 a^n % p
{
    LL ans = 1;
    while(n)
    {
        if(n & 1) ans = ans * a % p;
        a = a * a % p;
        n >>= 1;
    }
    return ans;
}
LL niYuan(LL a, LL b)   //费马小定理求逆元
{
    return pow(a, b - 2, b);
}
LL C(LL a, LL b)    //计算C(a, b)
{
    return Jc[a] * niYuan(Jc[b], mod) % mod
        * niYuan(Jc[a - b], mod) % mod;
}

三 常用公式及定理

1. 二项式定理:

( x + y ) n = ∑ C n i x n − i y i = ( n 0 ) x n + ( n 1 ) x n − 1 y + ⋯ + ( n n ) y n (x+y)^n=\sum C_n^i x^{n-i} y^i = \binom{n}{0}x^n+ \binom{n}{1} x^{n-1}y+ \cdots +\binom{n}{n}y^n (x+y)n=Cnixniyi=(0n)xn+(1n)xn1y++(nn)yn

2. 鸽巢原理:将 n + 1 n+1 n+1 个物品放到 n n n 个抽屉中,有一个抽屉至少有两个物品
3. 常见恒等式:

∑ i = 0 n ( n i ) = 2 n \sum_{i=0}^n \binom{n}{i}=2^n i=0n(in)=2n

∑ i = 0 n ( − 1 ) i ( n i ) = 0 \sum_{i=0}^n(-1)^i \binom{n}{i}=0 i=0n(1)i(in)=0

∑ i = 0 n 2 i ( n i ) = 3 n \sum_{i=0}^n2^i \binom{n}{i}=3^n i=0n2i(in)=3n

4. 帕斯卡恒等式:

C n + 1 k = C n k − 1 + C n k C_{n+1}^{k}=C_{n}^{k-1}+C_n^{k} Cn+1k=Cnk1+Cnk

5. L u c a s Lucas Lucas 定理推论: C n m C_n^m Cnm 为奇数 <=> n & m = m n\&m=m n&m=m
6. 容斥原理:

∣ A ∪ B ∣ = ∣ A ∣ + ∣ B ∣ − ∣ A ∩ B ∣ |A \cup B|=|A|+|B|-|A \cap B| AB=A+BAB

∣ A 1 ∪ A 2 ∪ ⋯ ∪ A n ∣ = ∑ ∣ A i ∣ − ∑ ∣ A i ∩ A j ∣ + ∑ ∣ A i ∩ A j ∩ A k ∣ − ⋯ + ( − 1 ) n − 1 ∣ A 1 ∩ ⋯ ∩ A n ∣ |A_1 \cup A_2 \cup \cdots \cup A_n |=\sum|A_i|-\sum|A_i \cap A_j|+ \sum|A_i \cap A_j \cap A_k|-\cdots+(-1)^{n-1}|A_1\cap\cdots\cap A_n| A1A2An=AiAiAj+AiAjAk+(1)n1A1An

7. 错排问题:

考虑一个有 n n n 个元素的排列,若一个排列中所有的元素都不在自己原来的位置上,那么这样的排列就称为原排列的一个错排。 n n n 个元素的错排数记为 D ( n ) D(n) D(n)
D n = n ! M n = n ! ( 1 2 ! − 1 3 ! + ⋯ + ( − 1 ) n 1 n ! ) D_n=n!M_n=n!(\frac{1}{2!}-\frac{1}{3!}+ \cdots +(-1)^n\frac{1}{n!}) Dn=n!Mn=n!(2!13!1++(1)nn!1)

简化公式:

D n = ⌊ n ! e + 0.5 ⌋ D_n=\lfloor \frac{n!}{e}+0.5\rfloor Dn=en!+0.5

递推公式:
D n = ( n − 1 ) ( D n − 1 + D n − 2 ) , n > 3 , D 1 = 0 , D 2 = 1 D_n=(n-1)(D_{n-1}+D_{n-2}),n>3,D_1=0,D_2=1 Dn=(n1)(Dn1+Dn2),n>3,D1=0,D2=1


四 常见数列及其性质

1. 斐波那契数列:

1.1 定义:

F i = F i − 1 + F i − 2 , F 1 = F 2 = 1 F_i=F_{i-1}+F_{i-2},F_1=F_2=1 Fi=Fi1+Fi2,F1=F2=1 的数列 { F n } \{F_n\} {Fn} 成为斐波那契数列, F n F_n Fn 为斐波那契数。

1.2 通项公式:

a n = 1 5 [ ( 1 + 5 2 ) n − ( 1 − 5 2 ) n ] a_n=\frac{1}{\sqrt{5}}[(\frac{1+\sqrt{5}}{2})^n - (\frac{1-\sqrt{5}}{2})^n] an=5 1[(21+5 )n(215 )n]

1.3 特殊形式公式:

F n ≡ 276601605 ( 69150401 3 n − 30849599 7 n ) ( m o d ( 1 e 9 + 9 ) ) Fn ≡ 276601605(691504013^n -308495997^n)(mod (1e9+9)) Fn276601605(691504013n308495997n)(mod(1e9+9)) 由二次剩余推导

1.4 性质:
  1. 平方与前后项:从第二项开始,每个奇数项的平方都比前后两项之积少 1,每个偶数项的平方都比前后两项之积多 1.

  2. 与集合子集: F n + 2 F_{n+2} Fn+2 同时也代表了集合 { 1 , 2 , … , n } \{1,2,…,n\} {1,2,,n} 中所有不包含相邻正整数的子集个数

  3. 奇数项求和: F 1 + F 3 + F 5 + . . . + F 2 n − 1 = F 2 n − F 2 + F 1 F_1+F_3+F_5+...+F_{2n-1} = F_{2n}-F_2+F_1 F1+F3+F5+...+F2n1=F2nF2+F1

  4. 偶数项求和: F 2 + F 4 + F 6 + . . . + F 2 n = F 2 n + 1 − F 1 F_2+F_4+F_6+...+F_{2n} = F_{2n+1}-F_1 F2+F4+F6+...+F2n=F2n+1F1

  5. 平方求和: F 1 2 + F 2 2 + . . . + F n 2 = F n ∗ F n + 1 F_1^2 +F_2^2 +...+F_n^2 = F_n*F_{n+1} F12+F22+...+Fn2=FnFn+1

  6. 两倍项关系: F 2 n / F n = F n − 1 + F n + 1 F_{2n}/F_n = F_{n-1}+F_{n+1} F2n/Fn=Fn1+Fn+1

  7. F ( n − 1 ) ∗ F ( n + 1 ) − F n 2 = ( − 1 ) n F(n-1)*F(n+1)-F_n^2 = (-1)^n F(n1)F(n+1)Fn2=(1)n

  8. 质数数量:
    每 3 个连续的数中有且只有一个被 2 整除
    每 4 个连续的数中有且只有一个被 3 整除,
    每 5 个连续的数中有且只有一个被 5 整除,
    每 6 个连续的数中有且只有一个被 8 整除,
    每 7 个连续的数中有且只有一个被 13 整除,
    每 8 个连续的数中有且只有一个被 21 整除,
    每 9 个连续的数中有且只有一个被 34 整除

  9. 尾数循环:
    个位数每 60 步一循环,后两位数每 300 步一循环,后三位数,每 1500 步一循环,后 4 位数每 15000 步一循环,后 5 位数每 150000 步一循环

2. 卡特兰数列:

2.1 计算公式:

h ( n ) = C 2 n n n + 1 h(n)=\frac{C_{2n}^n}{n+1} h(n)=n+1C2nn

h ( n ) = C 2 n n − C 2 n n − 1 h(n)=C_{2n}^n−C_{2n}^{n−1} h(n)=C2nnC2nn1

2.2 递推公式:

h ( n ) = h ( 0 ) ∗ h ( n − 1 ) + h ( 1 ) ∗ h ( n − 2 ) + ⋯ + h ( n − 1 ) ∗ h ( 0 ) ( n ≥ 2 ) h(n)=h(0)∗h(n−1)+h(1)∗h(n−2)+\cdots+h(n−1)∗h(0)(n\geq 2) h(n)=h(0)h(n1)+h(1)h(n2)++h(n1)h(0)(n2)

h ( n ) = h ( n − 1 ) × ( 4 n − 2 n + 1 ) h(n)=h(n−1)\times(\frac{4n−2}{n+1}) h(n)=h(n1)×(n+14n2)

C 0 = 1 , C n + 1 = 2 ( 2 n + 1 ) n + 2 C n , C 0 = 1 , C n + 1 = ∑ i = 0 n C i C n − i , n ≥ 0. C_0=1, C_{n+1}=\frac{2(2n+1)}{n+2}C_n, \\ C_0=1, C_{n+1}=\sum_{i=0}^nC_iC_{n-i},n \geq 0. C0=1,Cn+1=n+22(2n+1)Cn,C0=1,Cn+1=i=0nCiCni,n0.

2.3 常见用法:

卡特兰数的应用都可以归结到一种情况:有两种操作,分别为操作一和操作二,它们的操作次数相同,都为 N N N,且在进行第 K K K 次操作二前必须先进行至少 K K K 次操作一,问有多少中情况?结果就 C a t a l a n ( N ) Catalan (N) Catalan(N)

  1. 给定 n n n 个数,有多少种出栈序列
  2. n n n 个结点的二叉树有多少种构型
  3. n + 1 n+1 n+1 个叶子的满二叉树的个数
  4. n ∗ n n*n nn 的格子中,只在下三角行走,每次横或竖走一格,有多少种走法
  5. 将一个凸 n + 2 n+2 n+2 边型剖分成三角形的方法数
  6. 在圆上选择 2 n 2n 2n 个点,将这些点成对相连使得到的 n n n 条线段不相交的方法数
  7. n n n 个长方形填充一个高度为 n n n 的阶梯状图形的方法数
  8. n n n + 1 + 1 +1 n n n − 1 - 1 1 组成的排列中,满足前缀和 ≥ 0 \geq 0 0 的排列数
  9. 括号化问题,左括号和右括号各有 n n n 个时,合法的括号表达式的种类
  10. n + 1 n+1 n+1 个数连乘,乘法顺序的种类
  11. n n n 位二进制中,有 m m m 0 0 0,其余为 1 1 1,有 h ( C n m − C n m − 1 ) h(C_n^m-C_n^{m-1}) h(CnmCnm1)
  12. 将有 2 n 2n 2n 个元素的集合中的元素两两分为 n n n 个子集,若任意两个子集都不交叉,那么我们称此划分为一个不交叉划分。此时不交叉的划分数是 C a t a l a n ( N ) Catalan (N) Catalan(N)
  13. n n n 层的阶梯切割为 n n n 个矩形的切法数也是 C a t a l a n ( N ) Catalan (N) Catalan(N)
  14. 在一个 2 ∗ n 2*n 2n 的格子中填入 1 1 1 2 n 2n 2n 这些数值使得每个格子内的数值都比其右边和上边的所有数值都小的情况数也是 C a t a l a n ( N ) Catalan (N) Catalan(N)
n ≤ 35 n\leq35 n35 的卡特兰数
void init()
{
    int i,j;
    LL h[36];
    h[0]=h[1]=1;
    for(i=2;i<36;i++)
    {
      h[i]=0;
    for(j=0;j<i;j++)
            h[i]=h[i]+h[j]*h[i-j-1];
    }
}
n > 35 n>35 n>35 卡特兰数
//Lucas定理实现C(n,m)%p的代码:p为素数
LL exp_mod(LL a, LL b, LL p)
{ //快速幂取模
    LL res = 1;
    while(b != 0)
    {
        if(b&1) res = (res * a) % p;
        a = (a*a) % p;
        b >>= 1;
    }
    return res;
}
LL Comb(LL a, LL b, LL p)
{ //求组合数C(a,b)%p
    if(a < b)   return 0;
    if(a == b)  return 1;
    if(b > a - b)   b = a - b;
    LL ans = 1, ca = 1, cb = 1;
    for(LL i = 0; i < b; ++i)
    {
        ca = (ca * (a - i))%p;
        cb = (cb * (b - i))%p;
    }
    ans = (ca*exp_mod(cb, p - 2, p)) % p;
    return ans;
}
LL Lucas(LL n,LL m,LL p)
{ //Lucas定理求C(n,m)%p
     LL ans = 1;
     while(n&&m&&ans)
    {
        ans = (ans*Comb(n%p, m%p, p)) % p;
        n /= p;
        m /= p;
     }
     return ans;
}

五 递推方程

1. 线性递推方程:

F n − b 1 ∗ F n − 1 − b 2 ∗ F n − 2 − ⋯ − b k ∗ F n − k = 0 F_n−b_1∗F_{n−1}−b_2∗F_{n−2}−\cdots−b_k∗F_{n−k}=0 Fnb1Fn1b2Fn2bkFnk=0

其通项公式为:
F n = c 1 ∗ q 1 n + c 2 ∗ q 2 n + . . . + c k ∗ q k n F_n=c_1* q_1^n +c_2* q_2^n +...+c_k* q_k^n Fn=c1q1n+c2q2n+...+ckqkn
其中 q 1 ⋯ q n q_1\cdots q_n q1qn 是特征方程, q k − b 1 ∗ q ( k − 1 ) − b 2 ∗ q ( k − 2 ) − ⋯ − b k = 0 q^k -b_1* q^{(k-1)}- b_2* q^{(k-2)}-\cdots-b_k=0 qkb1q(k1)b2q(k2)bk=0 的根, c 1 ⋯ c k c_1\cdots c_k c1ck​ 是常数,由初值决定

2. 非线性递推方程:

F n − b 1 ∗ F n − 1 − b 2 ∗ F n − 2 − . . . − b k ∗ F n − k = S ( n ) F_n-b_1* F_{n-1}-b_2* F_{n-2}-...-b_k* F_{n-k}=S(n) Fnb1Fn1b2Fn2...bkFnk=S(n)

其通项公式为:
F n = c 1 ∗ q 1 n + c 2 ∗ q 2 n + . . . + c k ∗ q k n + f n F_n=c_1* q_1^n +c_2 * q_2^n +...+c_k*q_k^n+f_n Fn=c1q1n+c2q2n+...+ckqkn+fn

其中 q 1 . . . q n q_1...q_n q1...qn 是特征方程的根, f n f_n fn ​为一特解

3. 解递推方程:杜教 B M BM BM 模板,(黑科技)

给出前 k k k 项,即可求出第 n n n 项,只能用于符合递推方程形式的方程

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
#define rep(i,a,n) for (int i=a;i<n;i++)
#define per(i,a,n) for (int i=n-1;i>=a;i--)
#define pb push_back
#define mp make_pair
#define all(x) (x).begin(),(x).end()
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef vector<int> VI;
typedef long long ll;
typedef pair<int,int> PII;
const ll mod=1000000007;
ll powmod(ll a,ll b) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
// head
int _;
ll n;
namespace linear_seq {
    const int N=10010;
    ll res[N],base[N],_c[N],_md[N];
    vector<int> Md;
    void mul(ll *a,ll *b,int k) {
        rep(i,0,k+k) _c[i]=0;
        rep(i,0,k) if (a[i]) rep(j,0,k) _c[i+j]=(_c[i+j]+a[i]*b[j])%mod;
        for (int i=k+k-1;i>=k;i--) if (_c[i])
            rep(j,0,SZ(Md)) _c[i-k+Md[j]]=(_c[i-k+Md[j]]-_c[i]*_md[Md[j]])%mod;
        rep(i,0,k) a[i]=_c[i];
    }
    int solve(ll n,VI a,VI b) { // a 系数 b 初值 b[n+1]=a[0]*b[n]+...
        //        printf("%d\n",SZ(b));
        ll ans=0,pnt=0;
        int k=SZ(a);
        assert(SZ(a)==SZ(b));
        rep(i,0,k) _md[k-1-i]=-a[i];_md[k]=1;
        Md.clear();
        rep(i,0,k) if (_md[i]!=0) Md.push_back(i);
        rep(i,0,k) res[i]=base[i]=0;
        res[0]=1;
        while ((1ll<<pnt)<=n) pnt++;
        for (int p=pnt;p>=0;p--) {
            mul(res,res,k);
            if ((n>>p)&1) {
                for (int i=k-1;i>=0;i--) res[i+1]=res[i];res[0]=0;
                rep(j,0,SZ(Md)) res[Md[j]]=(res[Md[j]]-res[k]*_md[Md[j]])%mod;
            }
        }
        rep(i,0,k) ans=(ans+res[i]*b[i])%mod;
        if (ans<0) ans+=mod;
        return ans;
    }
    VI BM(VI s) {
        VI C(1,1),B(1,1);
        int L=0,m=1,b=1;
        rep(n,0,SZ(s)) {
            ll d=0;
            rep(i,0,L+1) d=(d+(ll)C[i]*s[n-i])%mod;
            if (d==0) ++m;
            else if (2*L<=n) {
                VI T=C;
                ll c=mod-d*powmod(b,mod-2)%mod;
                while (SZ(C)<SZ(B)+m) C.pb(0);
                rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod;
                L=n+1-L; B=T; b=d; m=1;
            } else {
                ll c=mod-d*powmod(b,mod-2)%mod;
                while (SZ(C)<SZ(B)+m) C.pb(0);
                rep(i,0,SZ(B)) C[i+m]=(C[i+m]+c*B[i])%mod;
                ++m;
            }
        }
        return C;
    }
    int gao(VI a,ll n) {
        VI c=BM(a);
        c.erase(c.begin());
        rep(i,0,SZ(c)) c[i]=(mod-c[i])%mod;
        return solve(n,c,VI(a.begin(),a.begin()+SZ(c)));
    }
};
int main() {
    for (scanf("%d",&_);_;_--) {
        scanf("%lld",&n);
        vector<int>a;
        a.push_back(1);
        a.push_back(3);
        a.push_back(5);
        a.push_back(7);
        printf("%d\n",linear_seq::gao(a,n-1));
    }
}

六 母函数:

http://www.wutianqi.com/?p=596
用于计算组合问题可能情况数

1. 普通母函数:

普通母函数通常解决类似如下的问题:
给 5 张 1 元,4 张 2 元,3 张 5 元,要得到 15 元,有多少种组合?
某些时候会规定至少使用 3 张 1 元、1 张 2 元、0 张 5 元。
某些时候会规定有无数张 1 元、2 元、5 元。

const int MAX=10000;
const int INF=0x3f3f3f3f;
//为计算结果,b为中间结果。
// K对应具体问题中物品的种类数。
//v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。
//n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。0
//n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。INF
//P为可能出现的最大指数(所求情况)
int a[MAX],b[MAX],P;
int k,v[MAX],n1[MAX],n2[MAX];
//初始化a
void cal(int n)       //n为种类数
{
    memset(a,0,sizeof(a));
    a[0]=1;
    for (int i=1;i<=n;i++)//循环每个因子
    {
        memset(b,0,sizeof(b));
        for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项
            for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项
                b[k+j*v[i]]+=a[k];//把结果加到对应位
        memcpy(a,b,sizeof(b));//b赋值给a
    }
}

12345678910111213141516171819202122232425

2. 指数母函数:

定义:对于序列 a 0 , a 1 , a 2 , ⋯ a_0,a_1,a_2,\cdots a0,a1,a2, ,函数
G e ( x ) = a 0 + a 1 1 ! x + a 2 2 ! x 2 + a 3 3 ! x 3 + ⋯ + a k k ! x k + ⋯ G_e(x)=a_0+ \frac{a_1}{1!}x+\frac{a_2}{2!}x^2+\frac{a_3}{3!}x^3+\cdots+\frac{a_k}{k!}x^k+\cdots Ge(x)=a0+1!a1x+2!a2x2+3!a3x3++k!akxk+
称为序列 a 0 , a 1 , a 2 , ⋯ a_0,a_1,a_2,\cdots a0,a1,a2, 对应的指数型母函数

这样,对于一个多重集,其中 a 1 a_1 a1 重复 n 1 n_1 n1 次, a 2 a_2 a2 重复 n 2 n_2 n2 次, … \dots a k a_k ak 重复 n k n_k nk 次,从中取出 r r r 个排列的不同排列数所对应的指数型母函数为:
G e ( x ) = ( 1 + x 1 ! + x 2 2 ! + ⋯ + x n 1 n 1 ! ) + ( 1 + x 1 ! + x 2 2 ! + ⋯ + x n 2 n 2 ! ) + ⋯ + ( 1 + x 1 ! + x 2 2 ! + ⋯ + x n k n k ! ) G_e(x)=(1+\frac{x}{1!}+\frac{x^2}{2!}+\cdots+\frac{x^{n_1}}{{n_1}!}) + (1+\frac{x}{1!}+\frac{x^2}{2!}+\cdots+\frac{x^{n_2}}{{n_2}!}) + \cdots + (1+\frac{x}{1!}+\frac{x^2}{2!}+\cdots+\frac{x^{n_k}}{{n_k}!}) Ge(x)=(1+1!x+2!x2++n1!xn1)+(1+1!x+2!x2++n2!xn2)++(1+1!x+2!x2++nk!xnk)
用于求多重排列数
如以下问题:
假设有 8 个元素,其中 a 1 a_1 a1 重复 3 次, a 2 a_2 a2 重复 2 次, a 3 a_3 a3 重复 3 次。从中取 r r r 个排列,求其排列数。
对于上面的问题 “假设有 8 个元素,其中 a 1 a_1 a1 重复 3 次, a 2 a_2 a2 重复 2 次, a 3 a_3 a3 重复 3 次。从中取 r r r 个排列,求其排列数。”:

double c1[N],c2[N];
LL fac[N];
void Fac()            //求阶乘
{
    fac[0]=1;
    for(int i=1;i<=N;i++)
        fac[i]=fac[i-1]*i;
}

int a[N];           //1~n每种的个数
void cal(int n,int m)          //有n种,取m个
{
    memset(c1,0,sizeof(c1));
    memset(c2,0,sizeof(c2));

    c1[0]=1.0/fac[0];
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=m;j++)
            for(int k=0;k+j<=m && k<=a[i];k++)
                c2[k+j]+=c1[j]/fac[k];
        for(int j=0;j<=m;j++)
            c1[j]=c2[j],c2[j]=0;
    }
}
ans=c1[m]*fac[m];           //取m个时的多重排列数

3. 整数拆分:

  1. 有序拆分:把自然数 n n n 拆分成 r r r 个自然数之和,方案数有 C n − 1 r − 1 C_{n-1}^{r-1} Cn1r1
  2. 无序拆分:把自然数 n n n 拆分成 m m m 个自然数之和,方案数有 F n m = F n − 1 m − 1 + F n − m m , F n 1 = F n n = 1 F_{n}^{m}=F_{n-1}^{m-1}+F_{n-m}^m,F_n^1=F_n^n=1 Fnm=Fn1m1+Fnmm,Fn1=Fnn=1
母函数法求无序拆分数
#include 
#include 
using namespace std;

const int MAX=10000;
const int INF=0x3f3f3f3f;
//为计算结果,b为中间结果。
// K对应具体问题中物品的种类数。
//v[i]表示该乘积表达式第i个因子的权重,对应于具体问题的每个物品的价值或者权重。
//n1[i]表示该乘积表达式第i个因子的起始系数,对应于具体问题中的每个物品的最少个数,即最少要取多少个。
//n2[i]表示该乘积表达式第i个因子的终止系数,对应于具体问题中的每个物品的最多个数,即最多要取多少个。
//P为可能出现的最大指数(所求情况)
int a[MAX],b[MAX],P;
int k,v[MAX],n1[MAX],n2[MAX];
//初始化a
void cal(int n)
{
    memset(a,0,sizeof(a));
    a[0]=1;
    for (int i=1;i<=n;i++)//循环每个因子
    {
        memset(b,0,sizeof(b));
        for (int j=n1[i];j<=n2[i]&&j*v[i]<=P;j++)//循环每个因子的每一项
            for (int k=0;k+j*v[i]<=P;k++)//循环a的每个项
                b[k+j*v[i]]+=a[k];//把结果加到对应位
        memcpy(a,b,sizeof(b));//b赋值给a
    }
}


int main()
{
    int n;
    memset(n1,0,sizeof(n1));
    memset(n2,INF,sizeof(n2));
    for(int i=0;i<=125;i++)
        v[i]=i;
    while(cin>>n)
    {
        P=n;
        cal(n);
        cout<<a[n]<<endl;
    }
    return 0;
}

七 Polya 计数:

B u r n s i d e Burnside Burnside 定理,在每一种置换群也就是等价群中的数量和除以置换群的数量,即非等价的着色数等于在置换群中的置换作用下保持不变的着色平均数。

P o l y a Polya Polya 定理:设 G ‾ \overline G G n n n 个对象的一个置换群,用 m m m 种颜色染图这 n n n 个对象,则不同的染色方案数为:
L = 1 ∣ G ‾ ∣ [ m c ( p 1 ‾ ) + m c ( p 2 ‾ ) + ⋯ + m c ( p g ‾ ) ] L=\frac{1}{|\overline G|}[m^{c({\overline {p_1}})} + m^{c({\overline {p_2}})} + \cdots + m^{c({\overline {p_g}})}] L=G1[mc(p1)+mc(p2)++mc(pg)]
其中 G ‾ = { P 1 ‾ , P 2 ‾ , ⋯   , P g ‾ } , c ( P k ‾ ) \overline G=\{ \overline {P_1},\overline {P_2}, \cdots,\overline {P_g} \},c(\overline {P_k}) G={P1,P2,,Pg},c(Pk) P k ‾ \overline {P_k} Pk 的循环节数。

1. 定义:

G G G 是有限集 X X X 上的置换群,点 a , b ∈ X a,b∈X a,bX 称为 “等价” 的,当且仅当,存在 π ∈ G π∈G πG 使得 π ( a ) = b π(a)=b π(a)=b,记为 a ∼ b a \sim b ab,这种等价条件下, X X X 的元素形成的等价类称为 G G G 的轨道,它是集 X X X 的一个子集, G G G 的任意两个不同的轨道之交是空集,所以置换群 G G G 的轨道全体是集合 X X X 的一个划分,构成若干个等价类,等价类的个数记为 L L L

2. Z k Z_k Zk ( K K K 不动置换类):

G G G 1 ⋯ n 1 \cdots n 1n 的置换群。若 K K K 1 ⋯ n 1 \cdots n 1n 中某个元素, G G G 中使 K K K 保持不变的置换的全体,记以 Z k Z_k Zk,叫做 G G G 中使 K K K 保持不动的置换类,简称 K K K 不动置换类。

3. E k E_k Ek (等价类):

G G G 1 … n 1…n 1n 的置换群。若 K K K 1 … n 1…n 1n 中某个元素, K K K G G G 作用下的轨迹,记作 E k E_k Ek。即 K K K G G G 的作用下所能变化成的所有元素的集合。
这个时候有: ∣ E k ∣ ∗ ∣ Z k ∣ = ∣ G ∣ |Ek|*|Zk|=|G | EkZk=G 成立 ( k = 1 , 2 , ⋯ n ) (k=1,2,\cdots n) (k=1,2,n)

4. C ( π ) C(π) C(π)

对于一个置换 π ∈ G π∈G πG, 及 a ∈ X a∈X aX,若 π ( a ) = a π(a)=a π(a)=a,则称 a a a π π π 的不动点。 π π π 的不动点的全体记为 C ( π ) C (π) C(π)。例如 π = ( 123 ) ( 3 ) ( 45 ) ( 6 ) ( 7 ) π=(123)(3)(45)(6)(7) π=(123)(3)(45)(6)(7) X = { 1 , 2 , 3 , 4 , 5 , 6 , 7 } X=\{1,2,3,4,5,6,7\} X={1,2,3,4,5,6,7}; 那么 C ( π ) = { 3 , 6 , 7 } C (π)=\{3,6,7\} C(π)={3,6,7} 3 3 3 个元素。

5. B u r n s i d e Burnside Burnside 引理:

L = 1 ∣ G ∣ ( Z 1 + Z 2 + Z 3 + Z 4 + ⋯ + Z k ) = 1 ∣ G ∣ ( C ( π 1 ) + C ( π 2 ) + C ( π 3 ) + . . . . . + C ( π n ) ) ( 其中 k ∈ X , π ∈ G ) L=\frac{1}{|G|} (Z_1+Z_2+Z_3+Z_4+\dots+Z_k)=\frac{1}{|G|}(C (π_1)+C (π_2)+C (π_3)+.....+C (π_n))(其中 k∈X,π∈G) L=G1(Z1+Z2+Z3+Z4++Zk)=G1(C(π1)+C(π2)+C(π3)+.....+C(πn))(其中kX,πG)

6. P o l y a Polya Polya 定理:

G = π 1 , π 2 , π 3 , ⋯   , π n G={π_1,π_2,π_3,\cdots,π_n} G=π1π2π3,,πn X = a 1 , a 2 , a 3 , ⋯   , a n X={a_1,a_2,a_3,\cdots ,a_n} X=a1,a2,a3,,an 上一个置换群,用 m m m 中颜色对 X X X 中的元素进行涂色,那么不同的涂色方案数为: 1 ∣ G ∣ ( m C ( π 1 ) + m C ( π 2 ) + m C ( π 3 ) + ⋯ + m C ( π k ) ) \dfrac{1}{|G|}(m^{C(π1)}+ m^{C(π2)}+ m^{C(π3)} +\cdots+m^{C(π_k)}) G1(mC(π1)+mC(π2)+mC(π3)++mC(πk)). 其中 C ( π k ) C(π_k) C(πk) 为置换 π k π_k πk 的循环节的个数。

通过 Ploya 定理求解方案数
#define N 100000
int prime[N];
bool is_prime[N];
 
int sieve(int n)
{
    int p = 0;
    for (int i = 0; i <= n; ++i) is_prime[i] = true;
    is_prime[0] = is_prime[1] = false;
    for (int i = 2; i <= n; ++i) {
        if (is_prime[i]) {
            prime[p++] = i;
            for (int j = 2 * i; j <= n; j += i)
                is_prime[j] = false;
        }
    }
    return p;
}
 
int phi(int n)
{
    int rea = n;
    for(int i = 0; prime[i] * prime[i] <= n; i++)
    {
        if(n % prime[i] == 0)
        {
            rea = rea - rea / prime[i];
            while (n % prime[i] == 0)  n /= prime[i];
        }
    }
    if(n > 1)
        rea = rea - rea / n;
    return rea;
}
 
ll polya(int m, int n)
{
    ll sum = 0;
    ll i;
    for (i = 1; i * i < n; ++i) {
        if (n % i == 0) {
            sum += phi(i) * pow(m, n / i);
            sum += phi(n / i) * pow(m, i);
        }
    }
    if (i * i == n) sum += phi(i) * pow(m, i);
    if (n & 1) sum += n * pow(m, n / 2 + 1);
    else sum += (pow(m, n / 2) + pow(m, n / 2 + 1)) * n / 2;
 
    return sum / 2 / n;
}

八 快速傅里叶变换 (FFT):

https://blog.csdn.net/ggn_2015/article/details/68922404
用于计算多项式乘法 O ( n l o g n ) O (nlogn) O(nlogn)

#include
#include
#include
#include
#include
#include
#include 
using namespace std;
typedef complex<double> cd;//复数类的定义
const int maxl=2094153;//nlogn的最大长度(来自leo学长的博客)
const double PI=3.14159265358979;//圆周率,不解释
cd a[maxl],b[maxl];//用于储存变换的中间结果
int rev[maxl];//用于储存二进制反转的结果
void getrev(int bit){
    for(int i=0;i<(1<<bit);i++){//高位决定二进制数的大小
        rev[i]=(rev[i>>1]>>1)|((i&1)<<(bit-1));
    }//能保证(x>>1)
}
void fft(cd* a,int n,int dft){//变换主要过程
    for(int i=0;i<n;i++){//按照二进制反转
        if(i<rev[i])//保证只把前面的数和后面的数交换,(否则数组会被翻回来)
            swap(a[i],a[rev[i]]);
    }
    for(int step=1;step<n;step<<=1){//枚举步长的一半
        cd wn=exp(cd(0,dft*PI/step));//计算单位复根
        for(int j=0;j<n;j+=step<<1){//对于每一块
            cd wnk(1,0);//!!每一块都是一个独立序列,都是以零次方位为起始的
            for(int k=j;k<j+step;k++){//蝴蝶操作处理这一块
                cd x=a[k];
                cd y=wnk*a[k+step];
                a[k]=x+y;
                a[k+step]=x-y;
                wnk*=wn;//计算下一次的复根
            }
        }
    }
    if(dft==-1){//如果是反变换,则要将序列除以n
        for(int i=0;i<n;i++)
            a[i]/=n;
    }
}
int output[maxl];
char s1[maxl],s2[maxl];

void getmuti()    //计算高精度大数乘法,输入两个数a,b
{
    scanf("%s%s",s1,s2);//读入两个数
    int l1=strlen(s1),l2=strlen(s2);//就算"次数界"
    int bit=1,s=2;//s表示分割之前整块的长度
    for(bit=1;(1<<bit)<l1+l2-1;bit++){
        s<<=1;//找到第一个二的整数次幂使得其可以容纳这两个数的乘积
    }
    for(int i=0;i<l1;i++){//第一个数装入a
        a[i]=(double)(s1[l1-i-1]-'0');
    }
    for(int i=0;i<l2;i++){//第二个数装入b
        b[i]=(double)(s2[l2-i-1]-'0');
    }
    getrev(bit);fft(a,s,1);fft(b,s,1);//dft
    for(int i=0;i<s;i++)a[i]*=b[i];//对应相乘
    fft(a,s,-1);//idft
    for(int i=0;i<s;i++){//还原成十进制数
        output[i]+=(int)(a[i].real()+0.5);//注意精度误差
        output[i+1]+=output[i]/10;
        output[i]%=10;
    }
    int i;
    for(i=l1+l2;!output[i]&&i>=0;i--);//去掉前导零
    if(i==-1)printf("0");//特判长度为0的情况
    for(;i>=0;i--){//输出这个十进制数
        printf("%d",output[i]);
    }
    putchar('\n');
}

void getpoly()    //计算多项式乘法
{
    int n,m;
    scanf("%d%d",&n,&m);    //输入量多项式最高项次数
    for(int i=0;i<=n;i++) scanf("%lf",&a[i].real());    //读入第一个多项式的系数(a0+a1*x+a2*x^2+a3*x^3+.....+an*x^n)
    for(int i=0;i<=m;i++) scanf("%lf",&b[i].real());    //读入第二个多项式的系数(b0+b1*x+b2*x^2+b3*x^3+.....+bn*x^n)
    int bit=0,s=1;//s表示分割之前整块的长度
    for(s=1;s<=n+m;s<<=1)
        bit++;
    getrev(bit);fft(a,s,1);fft(b,s,1);//dft
    for(int i=0;i<s;i++)a[i]*=b[i];//对应相乘
    fft(a,s,-1);//idft
    for(int i=0;i<=n+m;i++)
        printf("%d ",(int)(a[i].real()+0.5));   //表示乘完多项式各项的系数,(a0+a1*x+a2*x^3...)
}

int main(){
    getmuti();     //10*10=100
    getpoly();    //(1+2x)*(1+2x+x2)=1+4x+5x2+2x3
    return 0;
}

你可能感兴趣的:(算法)