通常来说,斜率优化DP的状态转移方程式为:ios
其中,\(val(i,j)\)包含\(i\)和\(j\)的乘积项。c++
对于此类问题,咱们通常会经过将该问题转化为维护凸包,经过规划进一步思考优化。优化
事实上,咱们更倾向于将方程变形,经过观察每一位优化。spa
在总结斜率优化DP以前,仍是要说一句,状态转移方程是才最关键的,状态转移方程想不出来,就完蛋。code
这道题首先咱们考虑定义\(dp[i]\)表明前\(i\)台机器划分最小代价。队列
因为咱们不知道前面最优解划分了多少批任务,所以,咱们采用费用提早计算的思想进行解题。get
那么,咱们有:string
能够看出,在方程最后,咱们直接将\(j\)以后的全部的\(C[k]\)在此处进行累加。由于若是后面的状态转移到了该状态,那么后面的费用必定包含该次断点的\(s\)乘之后面的\(C[k]\)。不如咱们提早计算好了,方便转移,避免状态冗长。it
接下来,咱们进行斜率优化的变形:io
不妨设最优解在\(j\)处取得,那么有
变形得:
注意到,咱们将\(dp[j]\)看做\(y\),将\(sumC[j]\)当作\(x\),那么咱们要作的是最小化截距\(dp[i]-sumT[i]*sumC[i]-s*sumC[n]\)。
事实上,上面那个变形并非惟一的。通常而言,将乘积项转化为\(kx\),把没有任何系数的项当作\(y\),其它项当作截距。
按照刚刚的表达式来说,咱们能够维护一个队列,队列中包括每一个“候选点”,用当前斜率\(sumT[i]-s\)进行查找,找到截距最小。接着,在更新完刚刚的值以后,咱们将新产生的值加入队列中,并维护凸包。
至于为何要维护凸包,咱们给出如下证实:
观察这张,中间那个点永远不多是最优势,所以,维护凸包,将这样的点干掉。
斜率优化细节仍是蛮多的,所以必定要注意几点:
另外这道题的特殊性在于:每一次的斜率单调递增,于是采起删除队首元素便可。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define pii pair <int, int> #define mp(x, y) make_pair(x, y) #define FOR(i, a, b) for(register int i = a; i <= b; ++ i) #define ROF(i, a, b) for(register int i = a; i <= b; -- i) using namespace std; const int N = 300000 + 5; typedef long long LL; int n, s, q[N]; LL T[N], C[N], sumT[N], sumC[N], dp[N]; int main() { scanf("%d %d", &n, &s); memset(sumT, 0, sizeof(sumT)); memset(sumC, 0, sizeof(sumC)); FOR(i, 1, n) { scanf("%lld %lld", &T[i], &C[i]); sumT[i] = sumT[i - 1] + T[i], sumC[i] = sumC[i - 1] + C[i]; } memset(dp, 0x3f, sizeof(dp)); dp[0] = 0; int head = 1, tail = 1; q[tail] = 0; FOR(i, 1, n) { while(head < tail && (dp[q[head + 1]] - dp[q[head]]) < (sumT[i] + s) * (sumC[q[head + 1]] - sumC[q[head]])) ++ head; dp[i] = dp[q[head]] + sumT[i] * (sumC[i] - sumC[q[head]]) + s * (sumC[n] - sumC[q[head]]); while(head < tail && (dp[i] - dp[q[tail]]) * (sumC[q[tail]] - sumC[q[tail - 1]]) < (dp[q[tail]] - dp[q[tail - 1]]) * (sumC[i] - sumC[q[tail]])) -- tail; q[++ tail] = i; } printf("%lld\n", dp[n]); return 0; }
若是这道题斜率不单调递增,那么咱们就二分最优值。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define CLR(a, x) memset(a, x, sizeof(a)) #define FOR(i, x, y) for(register int i = x; i <= y; ++ i) #define ROF(i, x, y) for(register int i = x; i <= y; -- i) #define pii pair <int, int> #define mp(x, y) make_pair(x, y) using namespace std; const int N = 3e5 + 5; typedef long long LL; int n, s, q[N]; LL T[N], C[N], sumT[N], sumC[N], dp[N]; int main() { scanf("%d %d", &n, &s); CLR(sumT, 0); CLR(sumC, 0); FOR(i, 1, n) { scanf("%lld %lld", &T[i], &C[i]); sumT[i] = sumT[i - 1] + T[i], sumC[i] = sumC[i - 1] + C[i]; } CLR(dp, 0x3f); int head = 1, tail = 1, L, R, mid; dp[0] = 0; q[head] = 0; FOR(i, 1, n) { L = head, R = tail; while(L < R) { mid = L + ((R - L) >> 1); if(dp[q[mid + 1]] - dp[q[mid]]< (sumT[i] + s) * (sumC[q[mid + 1]] - sumC[q[mid]])) L = mid + 1; else R = mid; } dp[i] = dp[q[L]] + sumT[i] * (sumC[i] - sumC[q[L]]) + s * (sumC[n] - sumC[q[L]]); while(head < tail && (dp[i] - dp[q[tail]]) * (sumC[q[tail]] - sumC[q[tail - 1]]) < (dp[q[tail]] - dp[q[tail - 1]]) * (sumC[i] - sumC[q[tail]])) -- tail; q[++ tail] = i; } printf("%lld\n", dp[n]); return 0; }
考虑用每一个小猫时间减去到\(1\)号点的距离,sort,而后这道题跟上一道题相似,而且按照上一道题的作法,这道题斜率优化部分稍微容易。
#include<algorithm> #include<iostream> #include<cstring> #include<cstdio> #include<cmath> #include<deque> #define FOR(i, x, y) for(register int i = x; i <= y; ++ i) #define ROF(i, x, y) for(register int i = x; i >= y; -- i) #define CLR(a, b) memset(a, b, sizeof(a)) #define pii pair <int, int> #define mp(x, y) make_pair(x, y) using namespace std; const int N = 1e5 + 5, P = 100 + 10; typedef long long LL; int n, m, p, D[N] = {}, q[N]; LL A[N], s[N], dp[P][N]; int main() { CLR(s, 0); scanf("%d %d %d", &n, &m, &p); FOR(i, 2, n) { scanf("%d", &D[i]); D[i] += D[i - 1]; } FOR(i, 1, m) { LL H, T; scanf("%d %lld", &H, &T); A[i] = T - D[H]; } int head = 1, tail = 1; sort(A + 1, A + m + 1); FOR(i, 1, m) s[i] = s[i - 1] + A[i]; CLR(dp, 0x3f); FOR(i, 0, p - 1) dp[i][0] = 0; FOR(i, 1, p) { head = tail = 1; q[tail] = 0; FOR(j, 1, m) { while(head < tail && (dp[i - 1][q[head + 1]] + s[q[head + 1]] - dp[i - 1][q[head]] - s[q[head]]) <= A[j] * (q[head + 1] - q[head])) ++ head; dp[i][j] = dp[i - 1][q[head]] + A[j] * (j - q[head]) - (s[j] - s[q[head]]); while(head < tail && (dp[i - 1][q[tail]] + s[q[tail]] - dp[i - 1][q[tail - 1]] - s[q[tail - 1]]) * (j - q[tail]) > (dp[i - 1][j] + s[j] - dp[i - 1][q[tail]] - s[q[tail]]) * (q[tail] - q[tail - 1])) -- tail; q[++ tail] = j; } } printf("%lld\n", dp[p][m]); return 0; }
这道题也同样,就是维护的时候必定要当心便可。
#include<iostream> #include<cstring> #include<cstdio> #include<cmath> #define FOR(i, x, y) for(register int i = x; i <= y; ++ i) #define ROF(i, x, y) for(register int i = x; i >= y; -- i) #define CLR(x, y) memset(x, y, sizeof(x)) using namespace std; const int N = 500000 + 7; typedef long long LL; int n, k, a[N], q[N]; LL b[N], s[N], dp[N]; LL d_x(int x, int y) { return a[x + 1] - a[y + 1]; } LL d_y(int x, int y) { return (dp[x] - dp[y]) - (s[x] - s[y]) + (b[x] - b[y]); } void prework() { CLR(a, 0), CLR(b, 0); CLR(s, 0), CLR(q, 0); a[n + 1] = 1 << 30; return; } int main() { int T; scanf("%d", &T); while(T --) { scanf("%d %d", &n, &k); prework(); FOR(i, 1, n) { scanf("%d", &a[i]); s[i] = s[i - 1] + a[i]; } FOR(i, 1, n) b[i] = 1ll * a[i + 1] * i; int head = 1, tail = 1; CLR(dp, 0x3f); q[tail] = 0; dp[0] = 0; FOR(i, k, n) { while(head < tail && d_y(q[head + 1], q[head]) <= i * d_x(q[head + 1], q[head])) ++ head; dp[i] = dp[q[head]] + s[i] - s[q[head]] - a[q[head] + 1] * (i - q[head]); if(i + 1 >= k * 2) { while(head < tail && d_y(q[tail], q[tail - 1]) * d_x(i - k + 1, q[tail]) >= d_y(i - k + 1, q[tail]) * d_x(q[tail], q[tail - 1])) -- tail; q[++ tail] = i - k + 1; } } printf("%lld\n", dp[n]); } return 0; }