「Bzoj 2726」「SDOI2012」任务安排 (DP 费用提前计算,斜率优化)

CH 5A01 (简化版)
Bzoj 2726 (斜率优化版)
题意:见上。
很容易想出二维DP,即$dp(i,j)$表示前$i$个任务分了$j$个的最小值。
本题没有限制分多少份,所以第二维我们想一想是不是不必要的?
删去第二维我们就不能之前前面执行了多少次$S$,那么就算不出来,所以这里有一种方法是将费用提前计算。
设$dp(i)$表示前$i$个任务最小值。

$$
dp(i)=\min(dp(j)+\sum_{k=1}^i T_k \times \sum_{k=j+1}^i C_k + S \times \sum_{k=j+1}^n C_k)
$$
注意到后面的$S \times \sum_{k=j+1}^n C_k)$,这个就是将费用提前计算了,因为选择了这样的方式必然会对后面的任务产生这样的贡献,所以可以提前计算。

然后式子显然能斜率优化。但是斜率不单增,在凸壳中二分即可。则Bzoj 2726可解。

知识点:
1、DP 费用提前计算
2、斜率优化实数会被卡精度,用叉积来判
3、二分

//CH 5A01
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db double
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace flyinthesky {
const int MAXN = 5000 + 5;
const LL INF = 2223372036854775808ll;
int n, s, T[MAXN], C[MAXN];
LL Tsum[MAXN], Csum[MAXN], dp[MAXN];
void clean() {
}
int solve() {
clean();
scanf("%d%d", &n, &s);
for (int i = 1; i <= n; ++i) scanf("%d%d", &T[i], &C[i]);
Tsum[0] = Csum[0] = 0;
for (int i = 1; i <= n; ++i) Tsum[i] = Tsum[i - 1] + T[i], Csum[i] = Csum[i - 1] + C[i];
for (int i = 0; i <= n; ++i) dp[i] = INF;
dp[0] = 0;
for (int i = 1; i <= n; ++i) {
for (int j = 0; j < i; ++j) {
LL tT = Tsum[i];
LL tC = Csum[i] - Csum[j], tC2 = Csum[n] - Csum[j];
dp[i] = min(dp[i], dp[j] + tT * tC + s * tC2);
}
}
cout << dp[n];
return 0;
}
}
int main() {
flyinthesky::solve();
return 0;
}

//Bzoj 2726
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<queue>
#include<cmath>
#define ms(i, j) memset(i, j, sizeof i)
#define LL long long
#define db long double
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace flyinthesky {
const LL MAXN = 300000 + 5;
LL n, S, tt[MAXN], cc[MAXN];
LL T[MAXN], C[MAXN], dp[MAXN], que[MAXN], l, r;
//db getK(LL a, LL b) {return 1.0 * (dp[a] - dp[b]) / (C[a] - C[b]);}
LL fnd(db w) {
if (l == r) return l;
LL x = 1, y = r; // 只到 r
while (x < y) {
LL mid = (x + y) >> 1;
//if (getK(que[mid], que[mid + 1]) <= w) x = mid + 1; else y = mid; 实数会被卡精度
if (dp[que[mid + 1]] - dp[que[mid]] <= w * (C[que[mid + 1]] - C[que[mid]])) x = mid + 1; else y = mid;
}
return x;
}
void clean() {
ms(T, 0), ms(C, 0), ms(dp, 0);
}
int solve() {
clean();
scanf("%lld%lld", &n, &S);
for (LL i = 1; i <= n; ++i) scanf("%lld%lld", &tt[i], &cc[i]);
for (LL i = 1; i <= n; ++i) C[i] = C[i - 1] + cc[i], T[i] = T[i - 1] + tt[i];
l = r = 1, que[1] = 0;
for (LL i = 1; i <= n; ++i) {
LL pos = fnd(S + T[i]);
dp[i] = dp[que[pos]] - (S + T[i]) * C[que[pos]] + T[i] * C[i] + S * C[n];
//while (l < r && getK(que[r], que[r - 1]) >= getK(i, que[r])) --r;
while (l < r && (dp[que[r]] - dp[que[r - 1]]) * (C[i] - C[que[r]]) >= (dp[i] - dp[que[r]]) * (C[que[r]] - C[que[r - 1]])) --r;
que[++r] = i;
}
cout << dp[n];
return 0;
}
}
int main() {
flyinthesky::solve();
return 0;
}
------ 本文结束 ------