BZOJ 2726 [SDOI2012] 任務安排

更好的閱讀體驗 【Press Here】

Problem

傳送門 >ω<

題目大意:
按順序給定 n 個子任務,每個任務用時 ti ,費用係數 fi
連續的多個(一個)子任務合成爲大任務,大任務的用時和費用係數爲所有子任務之和,啓動一個大任務需要時間 S ,每次進行一個大任務,其費用爲 結束時間 * 費用係數 ,問最小費用爲多少(大任務也要按照先後順序進行)

要求所有子任務都被包括在大任務之中

題面複雜得一匹…

Solution

因爲根據習慣我們用 f 來進行動態規劃,所以將 g f 互換一下

先從比較簡單的開始
sumg[i]g[i] 前綴和
sumt[i]r[i] 前綴和
f[i] 爲 將前 i 個小任務分成若干個大任務所需要的最小費用
我們先不考慮每次機器啓動的 S ,易得轉移

f[i]=min(f[j],sumt[i](sumg[i]sumg[j]))

經過觀察發現,每次加入一個 S 都會對後面每個點增加一個貢獻 sg[k](k>j) ,即 ssumg[n]sumg[j]

所以轉移方程化爲

f[i]=min(f[j]+sumt[i](sumg[i]sumg[j])+s(sumg[n]sumg[j]))

顯然這樣就能使用 O(n2) 的算法 通過 60% 的數據

那麼如何進一步降低複雜度呢?

假設現在 i 狀態從 k 狀態轉移比 j 狀態要優,則有

f[j]+sumt[i](sumg[i]sumg[j])+s(sumg[n]sumg[j]))>f[k]+sumt[i](sumg[i]sumg[k])+s(sumg[n]sumg[k]))

化簡可得

f[j]ssumg[j]sumt[i]sumg[j]>f[k]ssumg[k]sumt[i]sumg[k]

f[k]f[j]sumg[k]sumg[j]<s+sumt[i]

其實這就已經可以做了,顯然需要維護一個下凸包,在插入的時候彈出無用狀態

但是後面的斜率 s+sumt[i] 沒有單調性,所以說就不能夠從狀態頭部彈出節點,不能保證頭部狀態最優,所以在可能的狀態序列中二分查找,直到右側節點不優於當前節點爲止

代碼

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1000010;
#define mid ((l + r) >> 1)
ll t[N] , g[N];
ll f[N];
ll sta[N] , top;
ll n , s;
ll read() {
    ll ans = 0 , flag = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0') {if(ch == '-') flag = - 1; ch = getchar();}
    while(ch >= '0' && ch <= '9') {ans = ans * 10 + ch - '0'; ch = getchar();}
    return ans * flag;
}
bool check(int x , int i) {
    if(x == top) return 0;
    if(f[sta[x + 1]] - f[sta[x]] <= (g[sta[x + 1]] - g[sta[x]]) * (s + t[i])) return 1;
    return 0;
}
bool slope(int x , int y , int z) {
    return (f[x] - f[y]) * (g[y] - g[z]) <= (f[y] - f[z]) * (g[x] - g[y]);
}
int work(int i) {
    int l = 1 , r = top;
    if(l == r) return sta[l];
    while(l < r) {
        if(check(mid , i)) l = mid + 1;
        else r = mid;
    }
    return sta[l];
}
int main() {
    n = read(); s = read();
    for(int i = 1 ; i <= n ; ++ i) {
        t[i] = read(); t[i] += t[i - 1];
        g[i] = read(); g[i] += g[i - 1];
    }
    sta[++ top] = 0;
    for(int i = 1 ; i <= n ; ++ i) {
        int j = work(i);
        f[i] = f[j] + t[i] * (g[i] - g[j]) + s * (g[n] - g[j]);
        while(top >= 2) {
            if(slope(i , sta[top] , sta[top - 1])) sta[top --] = 0;
            else break;
        }
        sta[++ top] = i;
    }
    printf("%lld\n" , f[n]);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章