算法竞赛笔记——数学

数学

组合数学

1.球盒模型

(1)\(n\) 个 相同的球放入 \(k\) 个不同的盒子里,盒子允许为空:方案数 = \(\binom{n + k - 1}{k - 1}\) = \(\binom{n + k - 1}{n}\)

​ 例如:\(x_1 + x_2 + ... + x_k = n\) 中非负整数解的组数 (隔板法)

(2)\(n\) 个 相同的球放入 \(k\) 个不同的盒子里,盒子不允许为空:方案数 = \(\binom{n - 1}{k - 1}\)

​ 例如:\(x_1 + x_2 + ... + x_k = n\) 中正整数解的组数 (隔板法)

2.第二类斯特林数 \(O(k \cdot log(n))\)

有 \(n\) 个互不相同的球放入 \(k\) 个相同的盒子里,盒子不允许为空:

记号:\(S_2(n,k)\) 或 \({n \brace k}\)

递推式 :\(f(n, k) = k * f(n - 1, k) + f(n - 1, k - 1)\)

容斥:$S_2(n, k) = \frac{1}{k!} \sum_{i=0}^k (-1)^i \binom{k}{i} (k - i)^n $

证明:设事件 \(A_i\) 表示第 \(i\) 个盒子 没有球的方案数

则表示为 \(N((1-A_1)\ldots(1-A_k))\) = \(N((1 - A_1) \ldots (1 - A_k)) = \sum_{i=0}^k (-1)^i \binom{k}{i} (k - i)^n = S_2(n, k) \cdot k!\)

​ 即 $S_2(n,k)= \frac{1}{k!} \sum\limits_{i=0}^n (-1)^i \cdot \binom{k}{i} \cdot (k - i)^n $

重要的公式:$n^m = \sum\limits_{k=0}^m {n \brace k} n^{\underline{k}} $

​ 其中 \(n^{\underline{k}} = n(n-1)(n-2)\ldots(n-k+1)\) 表示 \(n\) 的 \(k\) 次下降幂(下降阶乘)

3.k-MEX

在 \(0\) 到 \(n - 1\) 这 \(n\) 个整数中随机选择 \(k\) 个不同的整数,组成的集合的 \(MEX\) 的期望是 \(\frac{\sum_{i=1}^k i \cdot \binom{n-i-1}{k-i}}{\binom{n}{k}} = \frac{k \cdot \binom{n}{k-1}}{\binom{n}{k}} = \frac{k}{n - k + 1}\)


容斥原理

\(|S - \bigcup\limits_{i=0}^n A_i| = \sum\limits_{i=0}^n(-1)^i \cdot \sum\limits_{1 \leq j_1<j_2<...<j_i \leq n}|{\bigcap\limits_{k=1}^iA_{j_k}}|\)


余数定理

\(N \ \% \ i = N - \lfloor N\ / \ i \rfloor \cdot i\)

/*
考虑整除分块
*/
vector<int> L, R, D, a;  //区间左右端点,等差数列公差,等差数列首项
int tot = 0;  // 总块数
for(int l = 1, r; l <= n; l = r + 1){
    r = n / (n / l); // 根据区间左端点算区间右端点!
    L.push_back(l);
    R.push_back(r);
    D.push_back(n / l);
    a.push_back(n % r);
    tot ++;
} 

线性基

#include <bits/stdc++.h>
using namespace std;

using u64 = unsigned long long;

struct LB {
  const int bit = 63;
  vector<u64> a = vector<u64>(bit + 1, 0);
  bool insert(u64 x){
    for(int i = bit; ~i; --i){
      if(!((x >> i) & 1)) continue;
      if(!a[i]){ a[i] = x; return true; }
      x ^= a[i];        
    }
    return false;
  }

  // 贪心法
  u64 query_max(){
    u64 ans = 0;
    for(int i = bit; ~i; --i){
      ans = max(ans, ans ^ a[i]);
    }
    return ans;
  }

  void normal(){
    for(int i = 0; i <= bit; i ++){
      for(int j = i + 1; j <= bit; j ++){
        if(a[j] >> i & 1){
          a[j] ^= a[i];
        }
      }
    }
  }

  u64 query_max_k(int k){
    k --;
    u64 ans = 0;
    for(int i = 0; i <= bit; i ++){
      if(k >> i & 1){
        ans ^= a[i];
      }
    }
    return ans;
  }

  vector<u64> b = vector<u64>(bit + 1, 0);
  void fennec(){ // 正交线性基
    normal();
    for(int i = 0; i <= bit; i ++){
      if(!a[i]){
        b[i] = (u64)1 << i;
        for(int j = i + 1; j <= bit; j ++){
          if(a[j] >> i & 1){
            b[i] |= (u64)1 << j;
          }
        }
      }
    }
  }
};

可持久化线性基

using u64 = unsigned long long;

struct LB {
  const int bit = 63;
  vector<u64> a = vector<u64>(bit + 1, 0);
  vector<int> v = vector<int>(bit + 1, 0);
  bool insert(u64 x, int t){
    for(int i = bit; ~i; --i){
      if(!((x >> i) & 1)) continue;
      if(!a[i]){ a[i] = x; return true;}
      x ^= a[i];
      if(v[i] < t){ a[i] ^= x; swap(v[i], t);}
    }
    return false;
  }

  // 贪心法
  u64 query_max(int t){
    u64 ans = 0;
    for(int i = bit; ~i; --i){
      if(t <= v[i]){
        ans = max(ans, ans ^ a[i]);
      }
    }
    return ans;
  }

};

实数线性基

using ld = long double;
const ld eps = 1e-5;
inline void solve() {
    int n, m;
    cin >> n >> m;
    vector<vector<ld>> v(n + 1, vector<ld>(m + 1));
    for(int i = 1; i <= n; i ++){
        for(int j = 1; j <= m; j ++){
            cin >> v[i][j];
        }
    }
    vector<int> cost(n + 1);
    for(int i = 1; i <= n; i ++){
        cin >> cost[i];
    }
    vector<int> vis(n + 1);
    int ans = 0, tot = 0;
    for(int i = 1; i <= m; i ++){
        int mi = INF, idx = -1;
        for(int j = 1; j <= n; j ++){
            if(vis[j]) continue;
            if(fabs(v[j][i]) > eps && cost[j] < mi){
                mi = cost[j];
                idx = j;
            }
        }
        if(idx == -1) continue;
        vis[idx] = 1;
        ans += mi;
        tot ++;
        for(int j = 1; j <= n; j ++){
            if(vis[j]) continue;
            ld k = v[j][i] / v[idx][i];
            for(int t = 1; t <= m; t ++){
                v[j][t] -= k * v[idx][t];
            }
        }
    }
    cout << tot << ' ' << ans << '\n';
}

欧拉函数

定义:欧拉函数 \(\varphi(n)\) 表示 \(区间[1,n]\) 中与 \(n\) 互质的数的个数。例如 \(\varphi(1) = 1\);当 \(n\) 是质数时,显然有 \(\varphi(n)=n-1\)

性质 \(1\):欧拉函数是积性函数,即对于任意满足 \(gcd(a, b) = 1\) 的整数 \(a, b\),有 \(\varphi(ab)=\varphi(a)\varphi(b)\) ,特别地,当 \(n\) 是奇数时 \(\varphi(2n)=\varphi(n)\)。

性质 \(2\):\(n = \sum\limits_{d|n} \varphi(d)\)

性质 \(3\):若 \(n = p^k\),其中 \(p\) 是质数,那么 \(\varphi(n) = p^k - p^{k-1}\)

性质 \(4\):由唯一分解定理,设 \(n = {p_1}^{\alpha_1}{p_2}^{\alpha_2}...{p_k}^{\alpha_k}\) ,其中 \(p_i\) 为质数,有 \(\varphi(n)=n(1 - \frac{1}{p_1})(1 - \frac{1}{p_2})...(1 - \frac{1}{p_k})\)

性质 \(5\):对任意不全为 \(0\) 的整数 \(m,n\),\(\varphi(mn)\varphi(gcd(m,n))=\varphi(m)\varphi(n)gcd(m,n)\)

*补充:\(n\) 的正约数个数 = \(\prod\limits_{i=1}^k (\alpha_i+1)\)

欧拉定理:若 \(a\) 与 \(b\) 互素,即 \(\gcd(a, b) = 1\),且 \(b \ge 1\),则 \(a^{\varphi(b)} \equiv 1 \pmod{b}\)


欧拉筛(线性筛)\(O(n)\)

vector<int> primes;
vector<bool> not_prime;
void get_primes(int n){
    not_prime.resize(n + 1);
    for(int i = 2; i <= n; i ++){
        if(!not_prime[i]) primes.push_back(i);
        for(int p : primes){
            if(p > n / i) break;
            not_prime[p * i] = true;
            if(i % p == 0) break;         
        }
    }
}

除数分解性质

设 \(p\) 和 \(q\) 是互质的正整数(即 \(\gcd(p, q) = 1\)),则 \(n=p \cdot q\) 的每个正除数 \(d\) 可唯一表示为:\(d = d_1 \cdot d_2\),
其中 \(d_1 \mid p\) (\(d_1\) 是 \(p\) 的正除数); \(d_2 \mid q\) (\(d_2\) 是 \(q\) 的正除数)

则 \(\sum\limits_{d|n}d = \sum\limits_{d|pq}d = \sum\limits_{d_1|p} \sum\limits_{d_2|q}d_1d_2=(\sum\limits_{d_1|p}d_1)(\sum\limits_{d_2|q}d_2)\)

可用此证明:若 \(f\) 为积性函数,则 \(g(n) = \sum\limits_{d \mid n}f(d) = \sum\limits_{d_1 \mid p} \sum\limits_{d_2 \mid q}f(d_1d_2) = \sum\limits_{d_1 \mid p} \sum\limits_{d_2|q}f(d_1)f(d_2) = (\sum\limits_{d_1 \mid p}f(d_1))(\sum\limits_{d_2 \mid q}f(d_2)) = g(p)g(q)\) 也是积性函数


积性函数

定义:如果函数 \(f : N \to R\) ,满足对于任意一对互质的正整数 \(p, q\),都有 \(f(pq)=f(p)f(q)\),则称 \(f\) 为积性函数。

(\(f(1) = 1\))特别地,如果函数 \(f\) 为完全积性函数,则 \(p, q\) 无需互质

例如:(1)欧拉函数 \(\varphi(n)\) 表示 \([1,n]\) 中与 \(n\) 互质的数的数量

​ (2)\(\tau(n)\) 或 \(d(n)\) 表示 \(n\) 的正因子的数量

​ (3)\(\sigma(n) % 小写希腊字母 sigma\) 表示 \(n\) 的约数之和

​ (4)莫比乌斯函数 $\mu(n) $

​ (5)单位函数 \(\varepsilon(n)\) (完全积性函数)

​ (6)幂函数 \(f(n) = n^N\) (完全积性函数)

​ (7)常数函数 \(f(n) = 1\) (完全积性函数)

性质:如果 \(f(n),g(n)\) 是积性函数,则 \(h(n)=f(n)g(n)\) 也是积性函数

结论:如果 \(f(n)\) 是积性函数,则 \(g(n)=\sum\limits_{d \mid n}f(d)\)(其中 \(d\) 是 \(n\) 的正因子)也是积性函数

计算法:假设 \(n = {p_1}^{\alpha_1}{p_2}^{\alpha_2}...{p_k}^{\alpha_k}\),则 \(f(n)=f({p_1}^{\alpha_1})f({p_2}^{\alpha_2})...f({p_k}^{\alpha_k})\)

/*
利用质因数分解求 f(n)
*/
int calc_f(int p, int a){ // 计算 f[p^a], 根据规则自定义(以d(n)为例)
    return a + 1;
}
i64 get_f(i64 n){
    if(n <= 1) return 1;
    i64 ans = 1;
    for(int i = 2; i <= n / i; i ++){
        int cnt = 0;
        while(n % i == 0) cnt ++, n /= i;
        ans *= calc_f(i, cnt); // f(p, k) = f(p^k)
    }
    if(n > 1) ans *= calc_f(n, 1);
    return ans;
}
/*
利用欧拉筛预处理 f(1),f(2),...,f(n)
*/
vector<int> f, cnt;
vector<int> primes;
vector<bool> not_prime;
int calc_f(int p, int a){ // 计算 f[p^a], 根据规则自定义(以d(n)为例)
    return a + 1;
}
void get_fn(int n){
    f.resize(n + 1);
    cnt.resize(n + 1);
    not_prime.resize(n + 1);
    f[1] = 1;
    for(int i = 2; i <= n; i ++){
        if(!not_prime[i]) primes.push_back(i), f[i] = calc_f(i, 1), cnt[i] = 1;
        for(int p : primes){
            if(p > n / i) break;
            not_prime[p * i] = true;
            if(i % p == 0){
                cnt[i * p] = cnt[i] + 1;
                f[i * p] = f[i] / calc_f(p, cnt[i]) * calc_f(p, cnt[i] + 1);
                break; 
            } 
            cnt[i * p] = 1;
            f[i * p] = f[i] * f[p];            
        }
    }
}

莫比乌斯反演

定理 \(1\):设 $f : N \to R $ ,\(g : N \to R\) 是两个函数,则 \( f(n) = \sum_{d \mid n} g(d) \ \Leftrightarrow \ g(n) = \sum_{d \mid n} \mu\left(\frac{n}{d}\right) f(d) = \sum_{d \mid n} \mu\left(d\right) f(\frac{n}{d}) \)

​ 或者 \(f(n) = \sum_{n \mid d} g(d) \ \Leftrightarrow \ g(n) = \sum_{n \mid d} \mu\left(\frac{d}{n}\right) f(d)\)

定理 \(2\):设 \(f:N \to R ,g:N \to R\) 是两个函数,且存在正整数 \(N\),对于所有的 \(n \gt N,f(n)=g(n)=0\)

则 \( f(n) = \sum_{\substack{n \mid m \\ m \leq N}} g(m) \ \Leftrightarrow \ g(n) = \sum_{\substack{n \mid m \\ m \leq N}} \mu\left(\frac{m}{n}\right) f(m) \)

性质:\( \sum_{d \mid n} \mu(d) = \begin{cases} 1 & n = 1, \\\\ 0 & n \neq 1 \end{cases} \) ,即 \(\sum_{d \mid n} \mu(d) = \varepsilon(n), \quad \mu * 1 = \varepsilon\)

这个性质意味着,莫比乌斯函数在狄利克雷生成函数中,等价于黎曼函数 \(\zeta(s) = \sum_{n=1}^{\infty} \frac{1}{n^s}\) 的倒数。所以在狄利克雷卷积中,莫比乌斯函数是常数函数 \(1\) 的逆元。

其中 \( \mu(n) = \begin{cases} 1 & \text{若 } n = 1, \\\\ 0 & \text{若 } n \text{ 含有平方因子}, \\\\ (-1)^k & \text{若 } k \text{ 为 } n \text{ 的本质不同质因子个数}. \end{cases} \) 为莫比乌斯函数(积性函数)

​ \(\varepsilon(n) = \begin{cases} 1 & n = 1, \\\\ 0 & n \neq 1 \end{cases}\) 为单位函数

补充结论(反演结论):\([gcd(i,j) = 1] = \varepsilon(\gcd(i,j)) = \sum_{d \mid \gcd(i,j)} \mu(d)\)

/*
利用欧拉筛预处理 f(1),f(2),...,f(n)
*/
vector<int> f;
vector<int> primes;
vector<bool> not_prime;

void get_Mu(int n){
    f.resize(n + 1);
    not_prime.resize(n + 1);
    f[1] = 1;
    for(int i = 2; i <= n; i ++){
        if(!not_prime[i]) primes.push_back(i), f[i] = -1;
        for(int p : primes){
            if(p > n / i) break;
            not_prime[p * i] = true;
            if(i % p == 0){
                f[i * p] = 0;
                break; 
            } 
            f[i * p] = f[i] * f[p];            
        }
    }
}

杂项

基姆拉尔森公式

const int d[]= {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
bool isLeap(int y) {
	return y % 400 == 0 || (y % 4 == 0 && y % 100 != 0);
}

int daysInMonth(int y, int m) {
	return d[m - 1] + (isLeap(y) && m == 2);
}

int getDay(int y, int m, int d) {
	int ans = 0;
	for (int i = 1970; i < y; i++) {
		ans += 365 + isLeap(i);
    }
	for (int i = 1; i < m; i++) {
		ans += daysInMonth(y, i);
	}
	ans += d;
	return (ans + 2) % 7 + 1;
}

等概率交换排列的两个元素,不动点个数的期望

对于一个初始为 \(1,2,3,...,n\) 的长度为 \(n\) 的排列,每次等概率交换两位,交换 \(k\) 次,则不动点个数的期望 \(E(X) = 1 + (n−1)(\frac{n^2-2n-2}{n^2})^k\),其中不动点定义为交换前后保持原位的元素

posted @ 2025-04-03 17:29  C₄H₈O  阅读(178)  评论(2)    收藏  举报