学习编程中的Ben 发表于 4 天前

斜率优化

自学一下斜率优化,有一定的理解难度,我理了好久思路
P2356 任务安排
// 易推出 dp 式: dp = dp + (sumc - sumc) * sumt + s * (sumc - sumc)
// dp = (s + sumt) * sumc + dp - sumc * sumt - s * sumc
// 转化为一次函数形式 y = k * x + b, y 为 dp, k 为(s + sumt), x 为 sumc,其余为 b
// 那么题意转化为 ---> 求 b 的最小值--->   求直线 y 的最小截距
// 对于所有已知点(sumc, dp), y 的 k 固定, y 的最小截距就是直线 y 向上平移第一个碰到的点产生的截距
// 因此维护一个下凸包即可, 即维护 (dp - dp) / (sumc - sumc) >= k 这么一个不等式,由于其的单调性易联想到单调队列
// 由于 t 可以为负数,因此 k 不一定单调递增,但是我们维护的 (dp - dp) / (sumc - sumc) 具有单调性,因此二分跳跃至正确决策点即可
// Created on 2025/8/10 20:37.
#include <bits/stdc++.h>
#define int long long
#define rep(i, j, k) for(int i = j; i <= k; i++)
#define reps(i, j, k) for(int i = j; i >= k; i--)
using namespace std;

const int _ = 3e5 + 7;
int n, s, c, t, f, st, l = 0, r = 0;

int x(int a, int b){
    return c - c];
}

int y(int a, int b){
    return f - f];
}

int get_pos(int i){
    int k1 = l, k3 = r, k = t + s;
    while (k1 < k3){
      int k2 = (k1 + k3) >> 1;
      if (y(k2, st) >= x(k2, st) * k) k3 = k2;
      else k1 = k2 + 1;
    }
    return k1;
}

signed main() {
    ios::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);

    cin >> n >> s;
    rep(i, 1, n){
      cin >> t >> c;
      t += t, c += c;
    }

    rep(i, 1, n){
      int pos = st;
      f = f + (c - c) * t + s * (c - c);
      while (l < r && y(r - 1, st) * x(r, i) >= y(r, i) * x(r - 1, st)) r--;
      st[++r] = i;
    }

    cout << f;

    return 0;
}


小小记录一下做的紫题,属于是很大进步了, 个人感觉这题从代码角度比 严格次小生成树 简单很多, 但是思想上难不少,考场还是喜欢 严格次小生成树 这类题目一点{:10_335:}

FishC 发表于 4 天前

根据你的描述,你正在学习斜率优化,并尝试解决任务安排问题(P5785)。你的推导思路正确,但代码存在一些问题。以下是修正后的简洁解答:

### 斜率优化核心思路
1. **状态转移方程**:
   `dp = min{ dp + sumT*(sumC-sumC) + s*(sumC-sumC) }`
   其中 `sumT = Σt`, `sumC = Σc`。

2. **斜率优化转换**:
   - 将方程变形为:`dp = (sumT + s) * sumC + (dp - sumT*sumC - s*sumC)`
   - 令 `y = dp`, `x = sumC`, `k_i = sumT + s`
   - 目标:找到点 `(x, y)` 使截距最小(即 `dp` 最小)

3. **维护下凸包**:
   - 用单调队列维护候选点集,保证相邻点斜率递增(下凸包性质)。
   - 由于 `k_i` 不单调(`t_i` 可能为负),需在凸包上二分查找第一个斜率 `≥k_i` 的线段左端点。

### 修正代码(附注释)
#include <iostream>
#define int long long
#define rep(i, a, b) for (int i = a; i <= b; i++)
using namespace std;

const int N = 3e5 + 10;
int n, s;
int sumT, sumC, dp, q;

// 斜率比较:判断 (q,q) 与 (q,i) 的斜率大小(避免浮点)
bool check(int a, int b, int c) {
    int x1 = sumC - sumC, y1 = dp - dp;
    int x2 = sumC - sumC, y2 = dp - dp;
    return y1 * x2 >= y2 * x1; // 若成立,则删除队尾b点
}

signed main() {
    cin >> n >> s;
    rep(i, 1, n) {
      int t, c;
      cin >> t >> c;
      sumT = sumT + t;
      sumC = sumC + c;
    }

    int l = 0, r = 0; // 队列初始化(q=0)
    q = 0;

    rep(i, 1, n) {
      int k = sumT + s; // 当前斜率k_i
      
      // 二分查找最优决策点
      int L = l, R = r;
      while (L < R) {
            int mid = (L + R) >> 1;
            // 比较 mid 与 mid+1 的斜率
            if (dp] - dp] >= k * (sumC] - sumC]))
                R = mid;
            else
                L = mid + 1;
      }
      int j = q; // 最优决策点j
      
      // 更新dp
      dp = dp + sumT * (sumC - sumC) + s * (sumC - sumC);
      
      // 维护下凸包(删除队尾不满足凸性的点)
      while (l < r && check(q, q, i)) r--;
      q[++r] = i; // 将i加入队尾
    }
    cout << dp << endl;
    return 0;
}

### 关键点说明
1. **凸包维护**:
   - 队列 `q` 存储决策下标,保证相邻点斜率递增(下凸包)。
   - 插入新点 `i` 时,若队尾两点 `(a,b)` 与 `(b,i)` 的斜率不满足凸性(即 `check()` 返回 `true`),则删除 `b`。

2. **二分查找**:
   - 在队列中二分找到第一个满足 `(dp-dp)/(sumC-sumC) >= k_i` 的 `j`,此时 `j` 是最优决策点。

3. **复杂度**:`O(n log n)`,适用于 `n ≤ 3e5`。

> 提示:若 `t_i ≥ 0`(`k_i` 单调递增),可直接用单调队列将复杂度降至 `O(n)`。本题因 `t_i` 可能为负,故需二分。

以上回复来自 FishC AI 小助理,如未能正确解答您的问题,请继续追问。

某一个“天” 发表于 3 天前

厉害
页: [1]
查看完整版本: 斜率优化