单调DP优化

掌握凸包技巧、分治优化等高级DP加速方法

概述

DP优化技术是解决大规模动态规划问题的关键。本章节深入探讨凸包技巧、分治优化、四边形不等式等高级优化方法,将DP复杂度从O(n²)降低到O(n log n)或O(n)。

核心内容

1. 凸包技巧优化

// 凸包技巧优化DP
#include <iostream>
#include <vector>
#include <deque>
using namespace std;

struct Line {
    long long k, b;
    int id;
    
    Line(long long _k, long long _b, int _id) : k(_k), b(_b), id(_id) {}
    
    long long eval(long long x) {
        return k * x + b;
    }
};

class ConvexHullTrick {
private:
    deque<Line> lines;
    
    // 检查是否需要删除中间直线
    bool bad(Line l1, Line l2, Line l3) {
        // 交点比较:(b2-b1)/(k1-k2) >= (b3-b2)/(k2-k3)
        return (l2.b - l1.b) * (l2.k - l3.k) >= (l3.b - l2.b) * (l1.k - l2.k);
    }
    
public:
    // 添加直线(斜率递减)
    void addLine(long long k, long long b, int id) {
        Line newLine(k, b, id);
        
        // 维护下凸包
        while (lines.size() >= 2 && bad(lines[lines.size()-2], lines[lines.size()-1], newLine)) {
            lines.pop_back();
        }
        lines.push_back(newLine);
    }
    
    // 查询最小值(x单调递增)
    pair<long long, int> queryMin(long long x) {
        // 移除不再最优的直线
        while (lines.size() >= 2 && lines[0].eval(x) >= lines[1].eval(x)) {
            lines.pop_front();
        }
        
        return {lines[0].eval(x), lines[0].id};
    }
    
    void clear() {
        lines.clear();
    }
};

// 应用:最小费用购买
long long minCostBuying(vector<long long>& cost, vector<long long>& amount) {
    int n = cost.size();
    vector<long long> dp(n + 1, LLONG_MAX);
    vector<long long> prefixSum(n + 1, 0);
    
    // 计算前缀和
    for (int i = 0; i < n; i++) {
        prefixSum[i + 1] = prefixSum[i] + amount[i];
    }
    
    dp[0] = 0;
    ConvexHullTrick cht;
    cht.addLine(0, 0, 0);
    
    for (int i = 1; i <= n; i++) {
        auto [minVal, fromIdx] = cht.queryMin(prefixSum[i]);
        dp[i] = minVal + cost[i - 1] * prefixSum[i];
        
        // 添加新的转移直线
        cht.addLine(-prefixSum[i], dp[i], i);
    }
    
    return dp[n];
}

// 应用:最优工厂选址
long long optimalFactoryPlacement(vector<long long>& positions, vector<long long>& demands) {
    int n = positions.size();
    vector<long long> dp(n, LLONG_MAX);
    
    ConvexHullTrick cht;
    
    for (int i = 0; i < n; i++) {
        if (i == 0) {
            dp[i] = 0;
        } else {
            auto [minVal, fromIdx] = cht.queryMin(positions[i]);
            dp[i] = minVal;
        }
        
        // 添加从当前位置的转移
        long long slope = -demands[i];
        long long intercept = dp[i] + demands[i] * positions[i];
        cht.addLine(slope, intercept, i);
    }
    
    return dp[n - 1];
}

int main() {
    // 测试最小费用购买
    vector<long long> cost = {2, 3, 1, 4};
    vector<long long> amount = {1, 2, 3, 1};
    
    cout << "最小购买费用: " 
         << minCostBuying(cost, amount) << endl;
    
    // 测试工厂选址
    vector<long long> positions = {1, 3, 6, 8};
    vector<long long> demands = {2, 1, 3, 2};
    
    cout << "最优工厂选址费用: " 
         << optimalFactoryPlacement(positions, demands) << endl;
    
    return 0;
}

2. 分治优化

// 分治优化DP
#include <iostream>
#include <vector>
#include <climits>
using namespace std;

class DivideConquerDP {
private:
    vector<vector<long long>> cost;
    vector<long long> dp, newDp;
    int n;
    
    // 计算转移费用
    long long getCost(int i, int j) {
        if (i > j) return LLONG_MAX;
        return cost[i][j];
    }
    
    // 分治计算DP值
    void solve(int l, int r, int optL, int optR) {
        if (l > r) return;
        
        int mid = (l + r) / 2;
        int optMid = optL;
        long long bestCost = LLONG_MAX;
        
        // 寻找最优转移点
        for (int k = optL; k <= min(mid - 1, optR); k++) {
            long long currentCost = dp[k] + getCost(k + 1, mid);
            if (currentCost < bestCost) {
                bestCost = currentCost;
                optMid = k;
            }
        }
        
        newDp[mid] = bestCost;
        
        // 递归处理左右两部分
        solve(l, mid - 1, optL, optMid);
        solve(mid + 1, r, optMid, optR);
    }
    
public:
    // 初始化
    DivideConquerDP(vector<vector<long long>>& costs) {
        cost = costs;
        n = cost.size();
        dp.resize(n);
        newDp.resize(n);
    }
    
    // 计算最优解
    long long solve() {
        // 初始状态
        for (int i = 0; i < n; i++) {
            dp[i] = getCost(0, i);
        }
        
        // 逐层计算
        for (int layer = 1; layer < n; layer++) {
            fill(newDp.begin(), newDp.end(), LLONG_MAX);
            solve(0, n - 1, 0, n - 1);
            dp = newDp;
        }
        
        return dp[n - 1];
    }
};

// 应用:最优二分图匹配
long long optimalMatching(vector<vector<long long>>& weights) {
    int n = weights.size();
    vector<vector<long long>> dp(n, vector<long long>(n, LLONG_MAX));
    
    // 预处理区间费用
    for (int i = 0; i < n; i++) {
        for (int j = i; j < n; j++) {
            long long sum = 0;
            for (int k = i; k <= j; k++) {
                sum += weights[k][k]; // 简化的费用计算
            }
            dp[i][j] = sum;
        }
    }
    
    DivideConquerDP dcDP(dp);
    return dcDP.solve();
}

// 应用:矩阵链乘法优化
class OptimizedMatrixChain {
private:
    vector<int> dims;
    vector<vector<long long>> cost;
    int n;
    
    void precomputeCosts() {
        cost.assign(n, vector<long long>(n, 0));
        
        for (int len = 2; len <= n; len++) {
            for (int i = 0; i <= n - len; i++) {
                int j = i + len - 1;
                cost[i][j] = LLONG_MAX;
                
                for (int k = i; k < j; k++) {
                    long long c = cost[i][k] + cost[k + 1][j] + 
                                 (long long)dims[i] * dims[k + 1] * dims[j + 1];
                    cost[i][j] = min(cost[i][j], c);
                }
            }
        }
    }
    
public:
    OptimizedMatrixChain(vector<int>& dimensions) {
        dims = dimensions;
        n = dims.size() - 1;
        precomputeCosts();
    }
    
    long long solve() {
        DivideConquerDP dcDP(cost);
        return dcDP.solve();
    }
};

int main() {
    // 测试最优匹配
    vector<vector<long long>> weights = {
        {1, 2, 3},
        {4, 5, 6},
        {7, 8, 9}
    };
    
    cout << "最优匹配费用: " 
         << optimalMatching(weights) << endl;
    
    // 测试矩阵链乘法
    vector<int> dimensions = {40, 20, 30, 10, 30};
    OptimizedMatrixChain omc(dimensions);
    
    cout << "优化后矩阵链乘法费用: " 
         << omc.solve() << endl;
    
    return 0;
}

解题技巧

🎯 优化识别

识别DP优化的适用条件:凸包技巧需要单调性,分治优化需要四边形不等式。

⚡ 复杂度分析

正确分析优化后的复杂度:凸包技巧O(n),分治优化O(n log n),选择合适的优化方法。

🔧 实现细节

注意数值精度和边界条件,正确维护数据结构的单调性质。

🧮 应用场景

适用于大规模DP问题:工厂选址、资源分配、路径优化等经典问题。