1. 程式人生 > 實用技巧 >[SDOI 2012]任務安排

[SDOI 2012]任務安排

Description

題庫連結

給定 \(n\) 個任務,第 \(i\) 個任務有兩個引數 \(T_i, C_i\),你現在要將這些任務分為相鄰的若干段,每一段任務需要同時完成。一段任務的用時為 \(\sum T_i+s\)\(s\) 給定),這一段任務會同時結束。依次執行每一段劃分好的任務。每一個任務的花費為該任務的結束時刻 \(\times C_i\)。問所有劃分中,最少花費和。

\(1\leq n\leq 3\times 10^5, 1\leq s\leq 2^8,0\leq |T_i|,C_i\leq 2^8\)

Solution

\(T, C\) 分別做字首和後,設 \(f_i\)

\(1\sim i\) 個任務劃分好後最少的花費。可以列出轉移方程 \[f_i=\min\{f_j+T_i(C_i-C_j)+s(C_n-C_j)\}\]

移項,可得 \(sC_j-f_j=-T_iC_j+(T_iC_i+sC_n-f_i)\),要最小化 \(f_i\),即最大化截距。因此將 \((C_j, sC_j-f_j)\) 這些點描繪在二維平面上後,去找到上凸包上與斜率為 \(-T_i\) 相切的點,這一點即為最優決策點。

由於 \(-T_i\) 不滿足單調性,因此需要在凸包上二分。

Code

#include <bits/stdc++.h>
#define y(i) (1ll*s*c[i]-f[i])
#define x(i) (1ll*c[i])
#define ll long long
using namespace std;
const int N = 3e5+5;

int n, s, t[N], c[N], q[N], head, tail;
ll f[N];

int main() {
    scanf("%d%d", &n, &s);
    for (int i = 1; i <= n; i++)
        scanf("%d%d", &t[i], &c[i]),
            t[i] += t[i-1], c[i] += c[i-1];
    for (int i = 1; i <= n; i++) {
        int l = head, r = tail-1, ans = tail, m;
        while (l <= r) {
            m = (l+r)>>1;
            if ((y(q[m])-y(q[m+1])) >= -1ll*t[i]*(x(q[m])-x(q[m+1]))) ans = m, r = m-1;
            else l = m+1;
        }
        int j = q[ans];
        f[i] = f[j]+1ll*t[i]*(c[i]-c[j])+1ll*s*(c[n]-c[j]);
        while (head < tail && (y(i)-y(q[tail-1]))*(x(i)-x(q[tail])) <= (y(i)-y(q[tail]))*(x(i)-x(q[tail-1]))) --tail;
        q[++tail] = i;
    }
    printf("%lld\n", f[n]);
    return 0;
}