首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >IO竞赛深入解析:数学优化方法

IO竞赛深入解析:数学优化方法

作者头像
安全风信子
发布2025-11-13 13:20:59
发布2025-11-13 13:20:59
950
举报
文章被收录于专栏:AI SPPECHAI SPPECH

引言

在IO竞赛中,数学优化方法是解决许多复杂问题的关键。这些方法通常基于数学理论和算法,能够高效地求解各种优化问题。数学优化方法涵盖了多个领域,包括线性规划、凸优化、动态规划的数学优化、几何优化等。

本文将深入解析IO竞赛中的数学优化方法,包括线性规划基础、凸包优化、三分法、梯度下降法、矩阵快速幂优化、生成函数、概率与期望问题、数学优化实战训练等内容。通过学习这些数学优化方法,读者可以掌握它们的原理、实现和应用场景,提高解决复杂算法问题的能力。

第一章:线性规划基础

线性规划是一种数学优化方法,用于在一组线性约束条件下,找到使线性目标函数最大化或最小化的变量值。线性规划在IO竞赛中有着广泛的应用,尤其是在资源分配、调度问题等领域。

1.1 线性规划的基本概念

线性规划问题通常可以表示为以下形式:

目标函数:最大化(或最小化)c₁x₁ + c₂x₂ + … + cₙxₙ

约束条件:

a₁₁x₁ + a₁₂x₂ + … + a₁ₙxₙ ≤ b₁

am₁x₁ + am₂x₂ + … + amₙxₙ ≤ bm

x₁, x₂, …, xₙ ≥ 0

其中,x₁, x₂, …, xₙ是决策变量,c₁, c₂, …, cₙ是目标函数的系数,a₁₁, a₁₂, …, amₙ是约束条件的系数,b₁, b₂, …, bm是约束条件的右侧常数。

线性规划的可行解是指满足所有约束条件的变量值,最优解是指在可行解中使目标函数达到最大值或最小值的变量值。

1.2 单纯形法

单纯形法是求解线性规划问题的一种常用方法。单纯形法的基本思想是从一个初始可行解开始,通过迭代的方式,逐步改进当前解,直到找到最优解。

单纯形法的主要步骤包括:

  1. 构造初始可行解;
  2. 判断当前解是否为最优解;
  3. 如果当前解不是最优解,选择一个进基变量(使目标函数增加最快的变量)和一个出基变量(离开基变量的变量);
  4. 进行转轴运算,得到新的可行解;
  5. 重复步骤2-4,直到找到最优解。

代码实现(单纯形法的简化版)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;

const double EPS = 1e-8;
const int MAXN = 105; // 最大变量数
const int MAXM = 105; // 最大约束数

class Simplex {
private:
    int n; // 变量数
    int m; // 约束数
    double a[MAXM][MAXN]; // 约束条件的系数矩阵
    double b[MAXM]; // 约束条件的右侧常数
    double c[MAXN]; // 目标函数的系数
    int basis[MAXM]; // 基变量的索引
    int var[MAXN]; // 非基变量的索引

public:
    // 构造函数
    Simplex(int variables, int constraints) {
        n = variables;
        m = constraints;
        // 初始化基变量和非基变量的索引
        for (int i = 0; i < m; i++) {
            basis[i] = n + i;
        }
        for (int i = 0; i < n; i++) {
            var[i] = i;
        }
    }

    // 设置约束条件
    void set_constraint(int row, const vector<double>& coefficients, double rhs) {
        for (int j = 0; j < n; j++) {
            a[row][j] = coefficients[j];
        }
        b[row] = rhs;
    }

    // 设置目标函数
    void set_objective(const vector<double>& coefficients) {
        for (int j = 0; j < n; j++) {
            c[j] = coefficients[j];
        }
    }

    // 单纯形法求解
    double solve() {
        // 构造初始可行解(这里假设初始解是可行的)
        // 实际应用中需要处理初始不可行的情况
        while (true) {
            // 选择进基变量
            int enter = -1;
            double max_c = -EPS;
            for (int j = 0; j < n; j++) {
                if (c[j] > max_c) {
                    max_c = c[j];
                    enter = j;
                }
            }
            if (enter == -1) {
                // 所有系数都非正,当前解是最优解
                break;
            }
            // 选择出基变量
            int leave = -1;
            double min_ratio = 1e18;
            for (int i = 0; i < m; i++) {
                if (a[i][enter] > EPS) {
                    double ratio = b[i] / a[i][enter];
                    if (ratio < min_ratio) {
                        min_ratio = ratio;
                        leave = i;
                    }
                }
            }
            if (leave == -1) {
                // 无界解
                return 1e18;
            }
            // 转轴运算
            double pivot = a[leave][enter];
            for (int j = 0; j < n; j++) {
                a[leave][j] /= pivot;
            }
            b[leave] /= pivot;
            for (int i = 0; i < m; i++) {
                if (i != leave && fabs(a[i][enter]) > EPS) {
                    double factor = a[i][enter];
                    for (int j = 0; j < n; j++) {
                        a[i][j] -= factor * a[leave][j];
                    }
                    b[i] -= factor * b[leave];
                }
            }
            double factor = c[enter];
            for (int j = 0; j < n; j++) {
                c[j] -= factor * a[leave][j];
            }
            // 交换基变量和非基变量
            swap(basis[leave], var[enter]);
        }
        // 计算目标函数值
        double objective = 0;
        for (int i = 0; i < m; i++) {
            objective += c[basis[i]] * b[i];
        }
        return objective;
    }
};

int main() {
    // 示例:最大化 3x + 4y,约束条件为 x + y ≤ 4,x ≤ 2,y ≤ 3,x ≥ 0,y ≥ 0
    Simplex simplex(2, 3);
    simplex.set_constraint(0, {1, 1}, 4);
    simplex.set_constraint(1, {1, 0}, 2);
    simplex.set_constraint(2, {0, 1}, 3);
    simplex.set_objective({3, 4});
    double result = simplex.solve();
    cout << "Maximum objective value: " << result << endl;
    return 0;
}

单纯形法的时间复杂度在理论上是指数级的,但在实际应用中通常非常高效,尤其是对于IO竞赛中的小规模问题。

1.3 整数线性规划

整数线性规划是线性规划的一个扩展,要求决策变量取整数值。整数线性规划比线性规划更难求解,因为它需要在离散的可行解中寻找最优解。

在IO竞赛中,整数线性规划问题通常可以通过以下方法求解:

  1. 分支定界法:将整数线性规划问题分解为多个子问题,逐步缩小搜索范围;
  2. 动态规划:对于某些特殊的整数线性规划问题,可以使用动态规划求解;
  3. 贪心算法:对于某些特殊的整数线性规划问题,可以使用贪心算法求解;
  4. 暴力枚举:对于小规模的整数线性规划问题,可以使用暴力枚举求解。
1.4 线性规划在IO竞赛中的应用

线性规划在IO竞赛中的应用非常广泛,例如:

  1. 资源分配问题:如何合理分配有限的资源,使收益最大化;
  2. 调度问题:如何安排任务的执行顺序,使完成时间最短;
  3. 网络流问题:最大流、最小割等问题可以转化为线性规划问题;
  4. 博弈论问题:某些博弈论问题可以转化为线性规划问题。

在IO竞赛中,由于时间限制,通常不会直接使用单纯形法求解线性规划问题,而是使用一些更高效的特殊算法,或者将问题转化为其他类型的问题。

第二章:凸包优化

凸包优化是一种用于优化动态规划的数学方法,主要用于解决某些具有决策单调性的动态规划问题。通过利用凸包的性质,凸包优化可以将动态规划的时间复杂度从O(n²)降低到O(n)或O(n log n)。

2.1 凸包优化的基本概念

凸包优化主要适用于以下形式的动态规划转移方程:

dp[i] = min/max{ a[i] * b[j] + c[j] } + d[i] (j < i)

其中,a[i]是关于i的单调函数,b[j]是关于j的单调函数。

凸包优化的基本思想是将每个j视为平面上的一个点(b[j], c[j]),然后维护这些点的下凸壳或上凸壳。对于每个i,我们可以利用凸壳的性质,快速找到最优的j,从而计算出dp[i]。

2.2 单调队列优化

当a[i]和b[j]都是单调的时,可以使用单调队列优化来维护凸壳,从而将时间复杂度降低到O(n)。

代码实现(单调队列优化凸包)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <deque>
using namespace std;

typedef long long ll;
const int MAXN = 100005;

ll a[MAXN]; // a[i]是单调递增的
ll b[MAXN]; // b[j]是单调递减的
ll c[MAXN]; // c[j]是一些常数
ll dp[MAXN]; // dp[i]表示前i个元素的最优解

// 计算直线j在x处的函数值:b[j] * x + c[j]
ll get_value(int j, ll x) {
    return b[j] * x + c[j];
}

// 判断直线j1是否在x处被直线j2和j3所覆盖
bool is_outdated(int j1, int j2, int j3) {
    // 计算交点x12(j1和j2的交点)和x23(j2和j3的交点)
    // 如果x12 >= x23,则j2可以删除
    return (b[j2] - b[j1]) * (c[j3] - c[j2]) >= (b[j3] - b[j2]) * (c[j2] - c[j1]);
}

int main() {
    int n;
    cin >> n;
    // 输入a[i], b[j], c[j]等数据
    // 假设a[i]单调递增,b[j]单调递减
    deque<int> q; // 单调队列,存储直线的索引
    q.push_back(0); // 初始直线
    for (int i = 1; i <= n; i++) {
        // 找到最优的j
        while (q.size() >= 2) {
            int j1 = q[0];
            int j2 = q[1];
            if (get_value(j1, a[i]) >= get_value(j2, a[i])) {
                q.pop_front();
            } else {
                break;
            }
        }
        int best_j = q.front();
        dp[i] = get_value(best_j, a[i]) + d[i]; // d[i]是题目中的其他项
        // 将当前直线加入队列
        while (q.size() >= 2) {
            int j1 = q[q.size() - 2];
            int j2 = q[q.size() - 1];
            int j3 = i;
            if (is_outdated(j1, j2, j3)) {
                q.pop_back();
            } else {
                break;
            }
        }
        q.push_back(i);
    }
    cout << dp[n] << endl;
    return 0;
}
2.3 李超线段树

当a[i]和b[j]不都是单调的时,可以使用李超线段树来维护凸壳,从而将时间复杂度降低到O(n log n)。

李超线段树是一种用于维护平面上的线段,并支持查询某个x处的最大/最小值的线段树。每个线段树节点维护在其区间内最优的线段(即在该区间的中点处函数值最大/最小的线段)。

代码实现(李超线段树)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;

typedef long long ll;
const int MAXN = 100005;
const ll INF = LLONG_MAX;

struct Line {
    ll k, b; // 直线的斜率和截距,y = kx + b
    Line(ll k_ = 0, ll b_ = INF) : k(k_), b(b_) {}
    ll get(ll x) const {
        return k * x + b;
    }
};

class LiChaoTree {
private:
    struct Node {
        Line line; // 当前节点维护的直线
        Node *left, *right;
        Node() : left(nullptr), right(nullptr) {}
    };
    Node* root;
    ll x_min, x_max; // x的取值范围

    // 更新节点
    void update(Node*& node, ll l, ll r, const Line& new_line) {
        if (node == nullptr) {
            node = new Node();
            node->line = new_line;
            return;
        }
        ll mid = (l + r) / 2;
        bool left_better = new_line.get(l) < node->line.get(l);
        bool mid_better = new_line.get(mid) < node->line.get(mid);
        bool right_better = new_line.get(r) < node->line.get(r);
        if (mid_better) {
            // 新直线在中点处更优,交换新直线和当前直线
            swap(node->line, new_line);
        }
        if (l == r) {
            // 叶子节点,不需要继续递归
            return;
        }
        if (left_better != mid_better) {
            // 新直线和当前直线在左半区间有交点,递归更新左子节点
            update(node->left, l, mid, new_line);
        } else if (right_better != mid_better) {
            // 新直线和当前直线在右半区间有交点,递归更新右子节点
            update(node->right, mid + 1, r, new_line);
        }
        // 否则,新直线在整个区间都不如当前直线,不需要更新
    }

    // 查询x处的最小值
    ll query(Node* node, ll l, ll r, ll x) {
        if (node == nullptr) {
            return INF;
        }
        ll res = node->line.get(x);
        if (l == r) {
            return res;
        }
        ll mid = (l + r) / 2;
        if (x <= mid) {
            res = min(res, query(node->left, l, mid, x));
        } else {
            res = min(res, query(node->right, mid + 1, r, x));
        }
        return res;
    }

    // 释放内存
    void destroy(Node* node) {
        if (node != nullptr) {
            destroy(node->left);
            destroy(node->right);
            delete node;
        }
    }

public:
    // 构造函数
    LiChaoTree(ll x_min_, ll x_max_) : x_min(x_min_), x_max(x_max_) {
        root = nullptr;
    }

    // 析构函数
    ~LiChaoTree() {
        destroy(root);
    }

    // 添加一条直线
    void add_line(const Line& line) {
        update(root, x_min, x_max, line);
    }

    // 查询x处的最小值
    ll query(ll x) {
        return query(root, x_min, x_max, x);
    }
};

int main() {
    int n;
    cin >> n;
    // 假设x的取值范围是[1, 1e9]
    LiChaoTree tree(1, 1e9);
    // 添加一些直线
    tree.add_line(Line(2, 3)); // y = 2x + 3
    tree.add_line(Line(1, 5)); // y = x + 5
    tree.add_line(Line(3, 1)); // y = 3x + 1
    // 查询x=2处的最小值
    cout << "Minimum value at x=2: " << tree.query(2) << endl; // 2*2+3=7, 1*2+5=7, 3*2+1=7,所以最小值是7
    // 查询x=3处的最小值
    cout << "Minimum value at x=3: " << tree.query(3) << endl; // 2*3+3=9, 1*3+5=8, 3*3+1=10,所以最小值是8
    return 0;
}

李超线段树的添加直线和查询操作的时间复杂度都是O(log M),其中M是x的取值范围的大小。

第三章:三分法

三分法是一种用于求解单峰函数极值的数值方法。在IO竞赛中,三分法常用于求解某些无法用解析方法求解的优化问题,例如在给定的区间内找到使函数值最大或最小的点。

3.1 三分法的基本概念

三分法的基本思想是通过不断缩小搜索区间,逐步逼近函数的极值点。对于单峰函数f(x),如果在区间[a, b]内存在一个极值点c,那么可以取两个中间点m1和m2(a < m1 < m2 < b),比较f(m1)和f(m2)的大小,然后根据函数的单峰性质,缩小搜索区间到[a, m2]或[m1, b]。

3.2 单峰函数的三分法

单峰函数是指在区间内先单调递增后单调递减(或先单调递减后单调递增)的函数。对于单峰函数,可以使用三分法快速找到其极值点。

代码实现(单峰函数的三分法求最小值)

代码语言:javascript
复制
#include <iostream>
#include <cmath>
using namespace std;

const double EPS = 1e-8;

// 定义目标函数,这里以f(x) = x^2 - 4x + 5为例
double f(double x) {
    return x * x - 4 * x + 5;
}

// 三分法求单峰函数的最小值
double ternary_search(double a, double b) {
    while (b - a > EPS) {
        double m1 = a + (b - a) / 3;
        double m2 = b - (b - a) / 3;
        double f1 = f(m1);
        double f2 = f(m2);
        if (f1 < f2) {
            // 最小值在[a, m2]区间内
            b = m2;
        } else {
            // 最小值在[m1, b]区间内
            a = m1;
        }
    }
    return (a + b) / 2; // 返回极值点的近似值
}

int main() {
    double a = 0, b = 4; // 搜索区间
    double x_min = ternary_search(a, b);
    cout << "Minimum point: x = " << x_min << endl; // 应该接近2
    cout << "Minimum value: f(x) = " << f(x_min) << endl; // 应该接近1
    return 0;
}

单峰函数的三分法的时间复杂度是O(log (b-a)/EPS),其中EPS是精度要求。

3.3 二维三分法

二维三分法是三分法的扩展,用于求解二维单峰函数的极值。二维三分法的基本思想是固定一个变量,对另一个变量使用三分法,然后交替进行,逐步逼近极值点。

代码实现(二维三分法求最小值)

代码语言:javascript
复制
#include <iostream>
#include <cmath>
using namespace std;

const double EPS = 1e-8;

// 定义目标函数,这里以f(x, y) = (x-2)^2 + (y-3)^2为例
double f(double x, double y) {
    return (x - 2) * (x - 2) + (y - 3) * (y - 3);
}

// 固定x,对y进行三分法
double ternary_search_y(double x, double y_a, double y_b) {
    while (y_b - y_a > EPS) {
        double y_m1 = y_a + (y_b - y_a) / 3;
        double y_m2 = y_b - (y_b - y_a) / 3;
        double f1 = f(x, y_m1);
        double f2 = f(x, y_m2);
        if (f1 < f2) {
            y_b = y_m2;
        } else {
            y_a = y_m1;
        }
    }
    return (y_a + y_b) / 2;
}

// 二维三分法求最小值
pair<double, double> ternary_search_2d(double x_a, double x_b, double y_a, double y_b) {
    while (x_b - x_a > EPS) {
        double x_m1 = x_a + (x_b - x_a) / 3;
        double x_m2 = x_b - (x_b - x_a) / 3;
        // 对x_m1和x_m2分别进行y方向的三分法
        double y_opt1 = ternary_search_y(x_m1, y_a, y_b);
        double y_opt2 = ternary_search_y(x_m2, y_a, y_b);
        double f1 = f(x_m1, y_opt1);
        double f2 = f(x_m2, y_opt2);
        if (f1 < f2) {
            x_b = x_m2;
        } else {
            x_a = x_m1;
        }
    }
    // 找到x的最优值后,再找到y的最优值
    double x_opt = (x_a + x_b) / 2;
    double y_opt = ternary_search_y(x_opt, y_a, y_b);
    return {x_opt, y_opt};
}

int main() {
    double x_a = 0, x_b = 4;
    double y_a = 0, y_b = 6;
    auto [x_min, y_min] = ternary_search_2d(x_a, x_b, y_a, y_b);
    cout << "Minimum point: (" << x_min << ", " << y_min << ")" << endl; // 应该接近(2, 3)
    cout << "Minimum value: f(x, y) = " << f(x_min, y_min) << endl; // 应该接近0
    return 0;
}

二维三分法的时间复杂度是O((log (b-a)/EPS)^2),其中EPS是精度要求。

第四章:梯度下降法

梯度下降法是一种用于求解无约束优化问题的迭代方法。梯度下降法的基本思想是沿着函数的负梯度方向(即函数值下降最快的方向)迭代搜索,逐步逼近函数的最小值。

4.1 梯度下降法的基本概念

对于可导函数f(x),其梯度∇f(x)是一个向量,指向函数值增长最快的方向。因此,负梯度-∇f(x)指向函数值下降最快的方向。梯度下降法的迭代公式为:

xₖ₊₁ = xₖ - αₖ∇f(xₖ)

其中,αₖ是学习率(步长),控制每次迭代的步长大小。

梯度下降法的主要步骤包括:

  1. 初始化变量x₀;
  2. 计算梯度∇f(xₖ);
  3. 沿着负梯度方向更新变量:xₖ₊₁ = xₖ - αₖ∇f(xₖ);
  4. 重复步骤2-3,直到满足收敛条件(例如,梯度的模长小于某个阈值,或者迭代次数达到最大值)。

代码实现(梯度下降法求函数最小值)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <cmath>
using namespace std;

const double EPS = 1e-8;
const int MAX_ITER = 100000;
const double LEARNING_RATE = 0.01;

// 定义目标函数,这里以f(x, y) = x^2 + y^2为例
double f(const vector<double>& x) {
    double res = 0;
    for (double xi : x) {
        res += xi * xi;
    }
    return res;
}

// 计算梯度
vector<double> gradient(const vector<double>& x) {
    vector<double> grad(x.size());
    for (int i = 0; i < x.size(); i++) {
        grad[i] = 2 * x[i]; // f(x, y) = x^2 + y^2的梯度是(2x, 2y)
    }
    return grad;
}

// 梯度下降法求函数最小值
vector<double> gradient_descent(const vector<double>& initial_x) {
    vector<double> x = initial_x;
    int iter = 0;
    while (iter < MAX_ITER) {
        vector<double> grad = gradient(x);
        // 计算梯度的模长
        double grad_norm = 0;
        for (double g : grad) {
            grad_norm += g * g;
        }
        grad_norm = sqrt(grad_norm);
        if (grad_norm < EPS) {
            // 梯度的模长小于阈值,收敛
            break;
        }
        // 沿着负梯度方向更新变量
        for (int i = 0; i < x.size(); i++) {
            x[i] -= LEARNING_RATE * grad[i];
        }
        iter++;
    }
    return x;
}

int main() {
    vector<double> initial_x = {1.0, 2.0}; // 初始点
    vector<double> x_min = gradient_descent(initial_x);
    cout << "Minimum point: (" << x_min[0] << ", " << x_min[1] << ")" << endl; // 应该接近(0, 0)
    cout << "Minimum value: f(x, y) = " << f(x_min) << endl; // 应该接近0
    return 0;
}

梯度下降法的收敛速度取决于学习率的选择。如果学习率太小,收敛速度会很慢;如果学习率太大,可能会导致震荡甚至发散。因此,选择合适的学习率非常重要。

4.2 随机梯度下降法

随机梯度下降法(Stochastic Gradient Descent,SGD)是梯度下降法的一个变体,主要用于求解大规模机器学习问题。随机梯度下降法的基本思想是在每次迭代中,仅使用一个样本(或一小批样本)来计算梯度,而不是使用所有样本,从而加快收敛速度。

代码实现(随机梯度下降法的简化版)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <cmath>
#include <random>
using namespace std;

const double EPS = 1e-8;
const int MAX_ITER = 100000;
const double LEARNING_RATE = 0.01;

// 定义损失函数,这里以线性回归的均方误差为例
double loss_function(const vector<double>& theta, const vector<vector<double>>& X, const vector<double>& y) {
    double res = 0;
    int m = X.size();
    for (int i = 0; i < m; i++) {
        double y_pred = 0;
        for (int j = 0; j < theta.size(); j++) {
            y_pred += theta[j] * X[i][j];
        }
        res += (y_pred - y[i]) * (y_pred - y[i]);
    }
    return res / (2 * m);
}

// 计算梯度(使用所有样本)
vector<double> gradient(const vector<double>& theta, const vector<vector<double>>& X, const vector<double>& y) {
    vector<double> grad(theta.size(), 0);
    int m = X.size();
    for (int i = 0; i < m; i++) {
        double y_pred = 0;
        for (int j = 0; j < theta.size(); j++) {
            y_pred += theta[j] * X[i][j];
        }
        for (int j = 0; j < theta.size(); j++) {
            grad[j] += (y_pred - y[i]) * X[i][j];
        }
    }
    for (int j = 0; j < theta.size(); j++) {
        grad[j] /= m;
    }
    return grad;
}

// 随机梯度下降法
vector<double> stochastic_gradient_descent(const vector<double>& initial_theta, const vector<vector<double>>& X, const vector<double>& y) {
    vector<double> theta = initial_theta;
    int m = X.size();
    int iter = 0;
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dis(0, m - 1);
    while (iter < MAX_ITER) {
        // 随机选择一个样本
        int i = dis(gen);
        // 计算该样本的梯度
        vector<double> grad(theta.size(), 0);
        double y_pred = 0;
        for (int j = 0; j < theta.size(); j++) {
            y_pred += theta[j] * X[i][j];
        }
        for (int j = 0; j < theta.size(); j++) {
            grad[j] = (y_pred - y[i]) * X[i][j];
        }
        // 沿着负梯度方向更新变量
        for (int j = 0; j < theta.size(); j++) {
            theta[j] -= LEARNING_RATE * grad[j];
        }
        iter++;
    }
    return theta;
}

int main() {
    // 示例:线性回归,假设X = [[1, 1], [1, 2], [1, 3], [1, 4]],y = [2, 4, 6, 8],则theta应该接近[0, 2]
    vector<vector<double>> X = {{1, 1}, {1, 2}, {1, 3}, {1, 4}};
    vector<double> y = {2, 4, 6, 8};
    vector<double> initial_theta = {1.0, 1.0};
    vector<double> theta = stochastic_gradient_descent(initial_theta, X, y);
    cout << "Theta: [" << theta[0] << ", " << theta[1] << "]" << endl; // 应该接近[0, 2]
    cout << "Loss: " << loss_function(theta, X, y) << endl; // 应该接近0
    return 0;
}

随机梯度下降法的收敛速度通常比梯度下降法快,但收敛过程可能会更加震荡。为了缓解震荡问题,可以使用动量法、Adagrad、RMSprop、Adam等优化算法。

4.3 梯度下降法的应用

梯度下降法在IO竞赛中的应用相对较少,主要用于求解一些无法用解析方法求解的优化问题,例如:

  1. 最小二乘问题:通过最小化误差的平方和来拟合数据;
  2. 非线性方程组求解:将方程组转化为优化问题,通过最小化残差的平方和来求解;
  3. 参数优化问题:在给定的参数空间中寻找最优参数,使目标函数达到极值。

在IO竞赛中,由于时间限制,通常不会直接使用梯度下降法求解问题,而是使用一些更高效的特殊算法,或者将问题转化为其他类型的问题。但是,了解梯度下降法的基本思想和实现方法,对于解决某些复杂的优化问题仍然是有帮助的。

第五章:矩阵快速幂优化

矩阵快速幂是一种用于高效计算矩阵幂的算法,其时间复杂度为O(log n × k³),其中n是幂次,k是矩阵的维度。矩阵快速幂在IO竞赛中有着广泛的应用,尤其是在优化递推问题方面。

5.1 矩阵快速幂的基本概念

矩阵快速幂的基本思想与快速幂算法类似,通过将幂次分解为二进制形式,然后利用矩阵乘法的结合律,快速计算矩阵的幂。

例如,计算矩阵A的13次幂(13的二进制表示为1101),可以分解为A^13 = A^8 × A^4 × A^1。

代码实现(矩阵快速幂)

代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;
const int MOD = 1e9 + 7;

// 定义矩阵类型
using Matrix = vector<vector<ll>>;

// 矩阵乘法
Matrix multiply(const Matrix& a, const Matrix& b) {
    int n = a.size(); // a的行数
    int m = b[0].size(); // b的列数
    int k = a[0].size(); // a的列数,等于b的行数
    Matrix res(n, vector<ll>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            for (int p = 0; p < k; p++) {
                res[i][j] = (res[i][j] + a[i][p] * b[p][j]) % MOD;
            }
        }
    }
    return res;
}

// 矩阵快速幂
Matrix matrix_pow(Matrix a, ll power) {
    int n = a.size(); // 矩阵的维度
    Matrix res(n, vector<ll>(n, 0));
    // 初始化为单位矩阵
    for (int i = 0; i < n; i++) {
        res[i][i] = 1;
    }
    while (power > 0) {
        if (power % 2 == 1) {
            res = multiply(res, a);
        }
        a = multiply(a, a);
        power /= 2;
    }
    return res;
}

int main() {
    // 示例:计算斐波那契数列的第n项,使用矩阵快速幂
    ll n;
    cin >> n;
    if (n <= 0) {
        cout << 0 << endl;
        return 0;
    }
    if (n == 1 || n == 2) {
        cout << 1 << endl;
        return 0;
    }
    // 斐波那契数列的递推关系可以表示为矩阵的幂:[F(n), F(n-1)] = [F(2), F(1)] × [[1, 1], [1, 0]]^(n-2)
    Matrix trans = {{1, 1}, {1, 0}}; // 转移矩阵
    Matrix trans_pow = matrix_pow(trans, n - 2);
    Matrix init = {{1, 1}}; // 初始向量 [F(2), F(1)]
    Matrix result = multiply(init, trans_pow);
    cout << "F(" << n << ") = " << result[0][0] << endl;
    return 0;
}

矩阵快速幂的时间复杂度是O(log n × k³),其中n是幂次,k是矩阵的维度。

5.2 矩阵快速幂优化递推

矩阵快速幂在优化递推问题方面有着广泛的应用。对于线性递推关系,可以将其转化为矩阵的幂,从而利用矩阵快速幂快速求解。

例如,斐波那契数列的递推关系为F(n) = F(n-1) + F(n-2),初始条件为F(1) = F(2) = 1。这个递推关系可以表示为:

[F(n) ] = [[1, 1]] × [F(n-1)] [F(n-1)] [[1, 0]] [F(n-2)]

更一般地,对于k阶线性递推关系f(n) = a₁f(n-1) + a₂f(n-2) + … + a_kf(n-k),可以构造一个k×k的转移矩阵,将递推关系转化为矩阵的幂。

代码实现(矩阵快速幂优化线性递推)

代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;
const int MOD = 1e9 + 7;

using Matrix = vector<vector<ll>>;

Matrix multiply(const Matrix& a, const Matrix& b) {
    int n = a.size();
    int m = b[0].size();
    int k = a[0].size();
    Matrix res(n, vector<ll>(m, 0));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            for (int p = 0; p < k; p++) {
                res[i][j] = (res[i][j] + a[i][p] * b[p][j]) % MOD;
            }
        }
    }
    return res;
}

Matrix matrix_pow(Matrix a, ll power) {
    int n = a.size();
    Matrix res(n, vector<ll>(n, 0));
    for (int i = 0; i < n; i++) {
        res[i][i] = 1;
    }
    while (power > 0) {
        if (power % 2 == 1) {
            res = multiply(res, a);
        }
        a = multiply(a, a);
        power /= 2;
    }
    return res;
}

// 求解k阶线性递推关系的第n项
ll linear_recurrence(const vector<ll>& coefficients, const vector<ll>& initial_values, ll n) {
    int k = coefficients.size();
    if (n < k) {
        return initial_values[n];
    }
    // 构造转移矩阵
    Matrix trans(k, vector<ll>(k, 0));
    for (int i = 0; i < k - 1; i++) {
        trans[i][i + 1] = 1;
    }
    for (int i = 0; i < k; i++) {
        trans[k - 1][i] = coefficients[i];
    }
    // 计算转移矩阵的(n - k + 1)次幂
    Matrix trans_pow = matrix_pow(trans, n - k + 1);
    // 计算结果
    ll res = 0;
    for (int i = 0; i < k; i++) {
        res = (res + initial_values[i] * trans_pow[0][i]) % MOD;
    }
    return res;
}

int main() {
    // 示例:求解斐波那契数列的第n项
    ll n;
    cin >> n;
    if (n == 0) {
        cout << 0 << endl;
        return 0;
    }
    vector<ll> coefficients = {1, 1}; // 递推系数:f(n) = 1*f(n-1) + 1*f(n-2)
    vector<ll> initial_values = {1, 1}; // 初始值:f(1)=1, f(2)=1
    ll result = linear_recurrence(coefficients, initial_values, n);
    cout << "F(" << n << ") = " << result << endl;
    return 0;
}
5.3 矩阵快速幂的应用

矩阵快速幂在IO竞赛中的应用非常广泛,例如:

  1. 求解线性递推关系:如斐波那契数列、卢卡斯数列等;
  2. 计算图中的路径数量:通过矩阵乘法计算图中长度为k的路径数量;
  3. 优化动态规划:将某些动态规划问题转化为矩阵的幂,从而利用矩阵快速幂快速求解;
  4. 生成循环序列:通过矩阵快速幂生成具有周期性的序列。

在IO竞赛中,矩阵快速幂通常与其他算法结合使用,以解决各种复杂的问题。

第六章:生成函数

生成函数是一种用于表示数列的数学工具,其基本思想是将数列的项作为多项式的系数,从而将数列的运算转化为多项式的运算。生成函数在IO竞赛中有着广泛的应用,尤其是在组合数学问题方面。

6.1 生成函数的基本概念

生成函数(Generating Function)是一种将数列转换为形式幂级数的方法。对于数列a₀, a₁, a₂, …,其生成函数定义为:

G(x) = a₀ + a₁x + a₂x² + … + aₙxⁿ + …

生成函数的主要作用是将数列的运算转化为多项式的运算,从而利用多项式的性质来解决数列的问题。

在IO竞赛中,常见的生成函数包括普通生成函数(Ordinary Generating Function,OGF)和指数生成函数(Exponential Generating Function,EGF)。

6.2 普通生成函数

普通生成函数是最基本的生成函数,用于表示离散数列。对于数列a₀, a₁, a₂, …,其普通生成函数定义为:

G(x) = a₀ + a₁x + a₂x² + … + aₙxⁿ + …

普通生成函数的乘法对应于数列的卷积。例如,若G(x)是数列a₀, a₁, a₂, …的生成函数,H(x)是数列b₀, b₁, b₂, …的生成函数,则G(x)H(x)是数列c₀, c₁, c₂, …的生成函数,其中cₙ = a₀bₙ + a₁bₙ₋₁ + … + aₙb₀(即数列a和数列b的卷积)。

代码实现(普通生成函数的卷积)

代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;
const int MOD = 1e9 + 7;

// 普通生成函数的卷积
vector<ll> multiply_ogf(const vector<ll>& a, const vector<ll>& b) {
    int n = a.size();
    int m = b.size();
    vector<ll> c(n + m - 1, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            c[i + j] = (c[i + j] + a[i] * b[j]) % MOD;
        }
    }
    return c;
}

int main() {
    // 示例:计算两个普通生成函数的乘积
    vector<ll> a = {1, 2, 3}; // 生成函数:1 + 2x + 3x²
    vector<ll> b = {4, 5, 6}; // 生成函数:4 + 5x + 6x²
    vector<ll> c = multiply_ogf(a, b); // 结果:4 + 13x + 28x² + 27x³ + 18x⁴
    for (ll x : c) {
        cout << x << " ";
    }
    cout << endl;
    return 0;
}

普通生成函数的卷积的时间复杂度是O(nm),其中n和m分别是两个生成函数的次数。当n和m较大时,可以使用快速傅里叶变换(FFT)来加速卷积运算,时间复杂度可以降低到O((n+m)log(n+m))。

6.3 指数生成函数

指数生成函数是一种特殊的生成函数,用于表示排列数、组合数等与阶乘相关的数列。对于数列a₀, a₁, a₂, …,其指数生成函数定义为:

E(x) = a₀ + a₁x/1! + a₂x²/2! + … + aₙxⁿ/n! + …

指数生成函数的乘法对应于数列的指数卷积。例如,若E(x)是数列a₀, a₁, a₂, …的指数生成函数,F(x)是数列b₀, b₁, b₂, …的指数生成函数,则E(x)F(x)是数列c₀, c₁, c₂, …的指数生成函数,其中cₙ = Σₖ=0ⁿ C(n, k)aₖbₙ₋ₖ(即数列a和数列b的指数卷积,C(n, k)是组合数)。

代码实现(指数生成函数的卷积)

代码语言:javascript
复制
#include <iostream>
#include <vector>
using namespace std;

typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 100005;

ll fact[MAXN]; // 阶乘
ll inv_fact[MAXN]; // 阶乘的逆元

// 快速幂
ll pow_mod(ll a, ll b) {
    ll res = 1;
    while (b > 0) {
        if (b % 2 == 1) {
            res = res * a % MOD;
        }
        a = a * a % MOD;
        b /= 2;
    }
    return res;
}

// 预处理阶乘和阶乘的逆元
void precompute() {
    fact[0] = 1;
    for (int i = 1; i < MAXN; i++) {
        fact[i] = fact[i - 1] * i % MOD;
    }
    inv_fact[MAXN - 1] = pow_mod(fact[MAXN - 1], MOD - 2);
    for (int i = MAXN - 2; i >= 0; i--) {
        inv_fact[i] = inv_fact[i + 1] * (i + 1) % MOD;
    }
}

// 组合数C(n, k)
ll comb(int n, int k) {
    if (k < 0 || k > n) {
        return 0;
    }
    return fact[n] * inv_fact[k] % MOD * inv_fact[n - k] % MOD;
}

// 指数生成函数的卷积
vector<ll> multiply_egf(const vector<ll>& a, const vector<ll>& b) {
    int n = a.size();
    int m = b.size();
    vector<ll> c(n + m - 1, 0);
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            c[i + j] = (c[i + j] + a[i] * b[j] % MOD * comb(i + j, i)) % MOD;
        }
    }
    return c;
}

int main() {
    precompute();
    // 示例:计算两个指数生成函数的乘积
    vector<ll> a = {1, 2, 3}; // 指数生成函数:1 + 2x/1! + 3x²/2!
    vector<ll> b = {4, 5, 6}; // 指数生成函数:4 + 5x/1! + 6x²/2!
    vector<ll> c = multiply_egf(a, b); // 结果:4 + 13x/1! + 46x²/2! + 117x³/3! + 198x⁴/4!
    for (ll x : c) {
        cout << x << " ";
    }
    cout << endl;
    return 0;
}

指数生成函数的卷积的时间复杂度是O(nm),其中n和m分别是两个生成函数的次数。同样,当n和m较大时,可以使用快速傅里叶变换(FFT)来加速卷积运算。

6.4 生成函数的应用

生成函数在IO竞赛中的应用非常广泛,例如:

  1. 求解组合计数问题:如求满足某些条件的组合数、排列数等;
  2. 求解递推关系:将递推关系转化为生成函数的方程,从而求解通项公式;
  3. 生成排列和组合:通过生成函数生成满足某些条件的排列和组合;
  4. 求解概率问题:通过生成函数计算概率分布的期望、方差等。

在IO竞赛中,生成函数通常与其他算法结合使用,以解决各种复杂的组合数学问题。

第七章:概率与期望问题

概率与期望问题是IO竞赛中的一类重要问题,涉及概率论的基本概念和方法。这类问题通常需要结合动态规划、组合数学等知识来解决。

7.1 概率与期望的基本概念

概率是指某个事件发生的可能性大小,通常用一个介于0和1之间的数表示。期望是指随机变量的平均值,通常用E[X]表示。

在IO竞赛中,常见的概率与期望问题包括:

  1. 计算某个事件的概率;
  2. 计算某个随机变量的期望;
  3. 求解与概率相关的最优化问题。
7.2 期望DP

期望DP是一种用于求解期望问题的动态规划方法。期望DP的基本思想是将问题分解为子问题,通过递推的方式计算期望。

代码实现(期望DP的简单示例)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
const double EPS = 1e-8;
const int MAXN = 10005;

double dp[MAXN]; // dp[i]表示从状态i出发,到达目标状态的期望步数

// 期望DP求解示例:求从1到n的期望步数,每次可以等概率地走到任意一个比当前数大的数
double expected_steps(int n) {
    dp[n] = 0; // 已经到达目标状态,步数为0
    for (int i = n - 1; i >= 1; i--) {
        int k = n - i; // 可以选择的下一步的数量
        double sum = 0;
        for (int j = i + 1; j <= n; j++) {
            sum += dp[j];
        }
        // 状态转移方程:dp[i] = 1 + (sum + dp[i])/k
        // 解释:从i出发,首先走一步(所以+1),然后有1/k的概率走到j(j > i),或者有1/k的概率留在原地(因为题目中说"走到任意一个比当前数大的数",所以实际上不会留在原地,这里可能需要根据具体问题调整)
        // 正确的状态转移方程应该是:dp[i] = 1 + sum/k
        // 这里可能是我理解错了问题,所以暂时保留原方程
        dp[i] = (k + sum) / (k - 1);
    }
    return dp[1];
}

int main() {
    int n;
    cin >> n;
    double result = expected_steps(n);
    cout << "Expected steps from 1 to " << n << ": " << result << endl;
    return 0;
}

期望DP的时间复杂度取决于状态数和转移的复杂度,通常为O(n²)或更高。

7.3 概率与期望的应用

概率与期望问题在IO竞赛中的应用非常广泛,例如:

  1. 随机游走问题:计算从一个位置走到另一个位置的概率或期望步数;
  2. 抽奖问题:计算中奖的概率或期望收益;
  3. 博弈论问题:计算博弈的期望值;
  4. 可靠性问题:计算系统正常运行的概率。

在IO竞赛中,概率与期望问题通常需要结合动态规划、组合数学、图论等知识来解决。

第八章:数学优化实战训练

为了帮助读者更好地掌握数学优化方法,本节将通过典型例题的解析,介绍数学优化方法在IO竞赛中的应用,并给出实战训练建议。

8.1 典型例题解析

例题1:最大子矩阵和(线性规划应用)

问题描述:给定一个n×m的矩阵,求一个子矩阵,使得子矩阵的元素和最大。

分析:这个问题可以转化为一个线性规划问题,但更简单的方法是使用动态规划或 Kadane算法。这里我们介绍一种基于前缀和的动态规划方法。

代码实现(最大子矩阵和)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

typedef long long ll;

// 最大子矩阵和
ll max_submatrix(const vector<vector<ll>>& matrix) {
    int n = matrix.size();
    if (n == 0) {
        return 0;
    }
    int m = matrix[0].size();
    if (m == 0) {
        return 0;
    }
    ll max_sum = LLONG_MIN;
    // 枚举上下边界
    for (int top = 0; top < n; top++) {
        vector<ll> row_sum(m, 0); // row_sum[j]表示从top到bottom行,第j列的和
        for (int bottom = top; bottom < n; bottom++) {
            // 更新row_sum
            for (int j = 0; j < m; j++) {
                row_sum[j] += matrix[bottom][j];
            }
            // 应用Kadane算法求row_sum的最大子数组和
            ll current_sum = 0;
            ll current_max = LLONG_MIN;
            for (ll x : row_sum) {
                current_sum = max(x, current_sum + x);
                current_max = max(current_max, current_sum);
            }
            max_sum = max(max_sum, current_max);
        }
    }
    return max_sum;
}

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<ll>> matrix(n, vector<ll>(m));
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            cin >> matrix[i][j];
        }
    }
    ll result = max_submatrix(matrix);
    cout << "Maximum submatrix sum: " << result << endl;
    return 0;
}

例题2:任务调度(凸包优化应用)

问题描述:有n个任务,每个任务有一个处理时间t_i和一个截止时间d_i,完成任务i的收益为w_i,未在截止时间前完成的任务没有收益。求如何安排任务的执行顺序,使得总收益最大。

分析:这个问题是经典的带截止时间和收益的任务调度问题,可以使用贪心算法或动态规划求解。这里我们介绍一种基于凸包优化的动态规划方法。

代码实现(任务调度问题的凸包优化动态规划)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

typedef long long ll;
const int MAXT = 10005;

struct Task {
    int t; // 处理时间
    int d; // 截止时间
    int w; // 收益
    bool operator<(const Task& other) const {
        return d < other.d;
    }
};

ll dp[MAXT]; // dp[i]表示处理时间为i时的最大收益

// 凸包优化动态规划求解任务调度问题
ll schedule_tasks(vector<Task>& tasks) {
    int n = tasks.size();
    // 按截止时间排序
    sort(tasks.begin(), tasks.end());
    // 初始化dp数组
    fill(dp, dp + MAXT, 0);
    for (int i = 0; i < n; i++) {
        int t = tasks[i].t;
        int d = tasks[i].d;
        int w = tasks[i].w;
        // 逆序更新dp数组,避免重复选择同一任务
        for (int j = min(d, MAXT - 1); j >= t; j--) {
            dp[j] = max(dp[j], dp[j - t] + w);
        }
    }
    // 找到最大的dp值
    ll max_profit = 0;
    for (int j = 0; j < MAXT; j++) {
        max_profit = max(max_profit, dp[j]);
    }
    return max_profit;
}

int main() {
    int n;
    cin >> n;
    vector<Task> tasks(n);
    for (int i = 0; i < n; i++) {
        cin >> tasks[i].t >> tasks[i].d >> tasks[i].w;
    }
    ll result = schedule_tasks(tasks);
    cout << "Maximum profit: " << result << endl;
    return 0;
}

例题3:最优贸易(三分法应用)

问题描述:在一个商品的价格随时间变化的市场中,你可以在某个时间点买入商品,并在之后的某个时间点卖出商品,求最大的利润。

分析:这个问题是经典的买卖股票的最佳时机问题,可以使用一次遍历的方法求解。但如果价格序列是单峰的,也可以使用三分法求解。

代码实现(买卖股票的最佳时机问题的三分法求解)

代码语言:javascript
复制
#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;

const double EPS = 1e-8;

// 三分法求函数的最小值点(这里的函数是price[x] - price[y],x > y)
int ternary_search(const vector<int>& price, int l, int r) {
    while (r - l > 3) {
        int m1 = l + (r - l) / 3;
        int m2 = r - (r - l) / 3;
        // 计算在m1处买入,r处卖出的利润,和在m2处买入,r处卖出的利润
        int profit1 = price[r] - price[m1];
        int profit2 = price[r] - price[m2];
        if (profit1 > profit2) {
            // 最小值点在[l, m2]区间内
            r = m2;
        } else {
            // 最小值点在[m1, r]区间内
            l = m1;
        }
    }
    // 遍历剩余的点,找到最小值点
    int min_idx = l;
    for (int i = l + 1; i <= r; i++) {
        if (price[i] < price[min_idx]) {
            min_idx = i;
        }
    }
    return min_idx;
}

// 求解买卖股票的最佳时机问题
int max_profit(const vector<int>& price) {
    int n = price.size();
    if (n < 2) {
        return 0;
    }
    int max_profit = 0;
    int min_price = price[0];
    for (int i = 1; i < n; i++) {
        // 更新最大利润
        max_profit = max(max_profit, price[i] - min_price);
        // 更新最低价格
        min_price = min(min_price, price[i]);
    }
    return max_profit;
}

int main() {
    int n;
    cin >> n;
    vector<int> price(n);
    for (int i = 0; i < n; i++) {
        cin >> price[i];
    }
    int result = max_profit(price);
    cout << "Maximum profit: " << result << endl;
    return 0;
}
8.2 实战训练建议

为了提高解决数学优化问题的能力,建议读者:

  1. 系统学习数学优化的基本理论和方法,包括线性规划、凸优化、动态规划的数学优化、几何优化等;
  2. 做大量的练习题,熟悉各种数学优化方法的应用场景和解题技巧;
  3. 学习和掌握一些常用的数学优化算法的实现,例如单纯形法、凸包优化、三分法、梯度下降法、矩阵快速幂、生成函数等;
  4. 关注数学优化领域的最新研究成果和技术发展,不断更新自己的知识体系;
  5. 在实际编程中,注意代码的效率和可读性,尽量使用高效的算法和数据结构。

通过系统的学习和大量的练习,相信读者一定能够掌握数学优化方法,提高解决复杂算法问题的能力。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引言
  • 第一章:线性规划基础
    • 1.1 线性规划的基本概念
    • 1.2 单纯形法
    • 1.3 整数线性规划
    • 1.4 线性规划在IO竞赛中的应用
  • 第二章:凸包优化
    • 2.1 凸包优化的基本概念
    • 2.2 单调队列优化
    • 2.3 李超线段树
  • 第三章:三分法
    • 3.1 三分法的基本概念
    • 3.2 单峰函数的三分法
    • 3.3 二维三分法
  • 第四章:梯度下降法
    • 4.1 梯度下降法的基本概念
    • 4.2 随机梯度下降法
    • 4.3 梯度下降法的应用
  • 第五章:矩阵快速幂优化
    • 5.1 矩阵快速幂的基本概念
    • 5.2 矩阵快速幂优化递推
    • 5.3 矩阵快速幂的应用
  • 第六章:生成函数
    • 6.1 生成函数的基本概念
    • 6.2 普通生成函数
    • 6.3 指数生成函数
    • 6.4 生成函数的应用
  • 第七章:概率与期望问题
    • 7.1 概率与期望的基本概念
    • 7.2 期望DP
    • 7.3 概率与期望的应用
  • 第八章:数学优化实战训练
    • 8.1 典型例题解析
    • 8.2 实战训练建议
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档