洛谷P2365 任务安排(斜率优化dp)

传送门

思路:
最朴素的dp式子很好考虑:设\(dp(i,j)\)表示前\(i\)个任务,共\(j\)批的最小代价。
那么转移方程就有:

\[dp(i,j)=min\{dp(k,j-1)+(sumT_i+S*j)*(sumC_i-sumC_k)\} \]

为什么有个\(S*j\)呢,因为前面的批次启动会对后面的答案有影响。
但是分析复杂度是\(O(n^3)\)的,肯定不行。
考虑一下为什么需要第二个状态呢?是为了消除后效性,因为后面的状态不知道总共启动了几次。
但我们可以把费用提前计算,一次启动,那么对于后面所有的机器都会有贡献,我们提前把这个贡献算了,后面就可以不管这个了,也就是强制消除后效性
所以变换后的\(dp\)式子就为:

\[dp(i)=min\{dp(j)+sumT_i*(sumC_i-sumC_j)+S*(sumC_n-sum_j)\} \]

其实这样已经可以通过洛谷的数据了,但这还不够!我们还可以优化。
观察\(dp\)式子,后面加上的部分为\(i,j\)相关变量的乘积形式,所以我们可以考虑斜率优化dp。
\(i,j\)变量分离,有:

\[dp(j)=(s+sumT_i)*sumC_j+dp_i-sumT_i*sumC_i-s*sumC_n \]

显然这个式子直接用队列维护一个斜率不断增加的下凸壳即可。
时间复杂度就为\(O(n)\)了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005, MOD = 1e9 + 7;
int n, s;
int sumt[N], sumc[N];
int q[N], dp[N];
int main() {
#ifdef heyuhhh
    freopen("input.in", "r", stdin);
#else
    ios::sync_with_stdio(false); cin.tie(0);
#endif
    cin >> n >> s;
    for(int i = 1; i <= n; i++) {
        int t, c; cin >> t >> c;
        sumt[i] = sumt[i - 1] + t;
        sumc[i] = sumc[i - 1] + c;
    }
    int l = 1, r = 1; q[1] = 0;
    for(int i = 1; i <= n; i++) {
        while(l < r && dp[q[l + 1]] - dp[q[l]] <= (s + sumt[i]) * (sumc[q[l + 1]] - sumc[q[l]])) ++l;
        dp[i] = dp[q[l]] - sumt[i] * sumc[q[l]] - s * sumc[q[l]] + sumt[i] * sumc[i] + s * sumc[n];
        while(l < r && (dp[i] - dp[q[r]]) * (sumc[q[r]] - sumc[q[r - 1]]) <= (dp[q[r]] - dp[q[r - 1]]) * (sumc[i] - sumc[q[r]])) --r;
        q[++r] = i;
    }
    cout << dp[n];
    return 0;
}

posted @ 2019-08-26 22:18  heyuhhh  阅读(259)  评论(0编辑  收藏  举报