【斜率优化】任务安排
** 斜率优化**
- 任务安排1 P2365 O(n^2)
时间为 t 费用为 c
状态表示:
f[i] 表示 将前i个任务分为若干批执行的费用+之后的启动时间 的最小值
重要思想:
1. 费用提前计算: 处理 s 对后面的任务的影响(启动的影响)
状态计算:
枚举最后一批 i i n
f[i] = min{f[q[hh]] + (∑ t[k])*(∑ c[k]) + s*(∑ c[k]) | j∈[0 i-1]}
k=1 k=q[hh]+1 k=q[hh]+1
完成这个任务 这个任务及之后的任务的启动时间
技巧: 分为变化的部分和不变化的部分
边界: f[0] = 0
- 任务安排2
斜率优化 O(n) t,c>0
f[i] = min{f[q[hh]] + sumt[i] * (sumc[i] - sumc[q[hh]]) + s * (sumc[n] - sumc[q[hh]])}
设 q[hh] 为使 f[q[hh]] + sumt[i] * (sumc[i] - sumc[q[hh]]) + s * (sumc[n] - sumc[q[hh]])最小的 q[hh]
f[i] = f[q[hh]] + sumt[i] * sumc[i] - sumt[i] * sumc[q[hh]] + s * sumc[n] - s * sumc[q[hh]]
f[q[hh]] = (sumt[i] + s) * sumc[q[hh]] + (f[i] - sumt[i] * sumc[i] - s * sumc[i])
其中 f[i] 为参数
看成y 看成斜率k 看成x 看成截距b
↑ 什么样的点不会被用到?
| /斜率固定, 凸包内部的点
| o o / 在一个单调的队列中找到第一个≥某一个数的点
| o /
| o o 1.斜率单调递增,横坐标单调递增
| / * 查询时 将队头斜率 <= 当前斜率的点 全部删掉
+----/--------→ * 插入时 将队尾不在凸包上的点全部删掉
/
* 任务安排3 P5785 2.斜率不一定单调递增,横坐标单调递增
斜率优化2 O(nlogn) t,c>0 * 查询时 二分查找
* 插入时 将队尾不在凸包上的点全部删掉
点击查看代码
#include <stdio.h> // f[i]为前i个任务答案,费用提前计算
#include <string.h> // f[i]=min{f[j]+(sumc[i]-sumc[j])sumt[i] + s*(sumc[n]-sumc[j])}
const int N = 5005;
typedef long long LL;
LL min(LL x, LL y) { return x < y ? x : y; }
int n, s; LL f[N], sumt[N], sumc[N];
int main() {
scanf("%d%d", &n, &s);
for(int i = 1, t, c; i <= n; i ++)
scanf("%d%d", &t, &c), sumt[i] = sumt[i-1] + t, sumc[i] = sumc[i-1] + c;
memset(f, 0x3f, sizeof(f)), f[0] = 0;
for(int i = 1; i <= n; i ++)
for(int j = 0; j < i; j ++)
f[i] = min(f[i], f[j] + LL(sumc[i] - sumc[j]) * sumt[i] + s * LL(sumc[n] - sumc[j]));
printf("%lld\n", f[n]);
return 0;
}
点击查看代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <utility>
#include <queue>
using namespace std;
typedef long long LL;
const int N = 3e5 + 5;
int n, s;
LL sumt[N], sumc[N];
LL f[N];
int q[N];
/*
int main() {
scanf("%d%d", &n, &s);
for(int i = 1, t, c; i <= n; i ++) {
scanf("%d%d", &t, &c);
sumt[i] = sumt[i - 1] + t;
sumc[i] = sumc[i - 1] + c;
}
int hh = 0, tt = 0;
q[0] = 0; // 队列[hh,tt]
for(int i = 1; i <= n; i ++) { // 队头的斜率 当前斜率
// 队列中有两个元素 (f[q[hh + 1]] - f[q[hh]]) / (sumc[q[hh + 1]] - sumc[q[hh]]) <= (sumt[i] + s)
while(hh < tt && (f[q[hh + 1]] - f[q[hh]]) <= (sumt[i] + s) * (sumc[q[hh + 1]] - sumc[q[hh]])) hh ++;
f[i] = f[q[hh]] - (sumt[i] + s) * sumc[q[hh]] + sumt[i] * sumc[i] + s * sumc[n]; // 此时的 q[hh] 为最优 j
// 队列中有两个元素 (f[q[tt]] - f[q[tt - 1]]) / (sumc[q[tt]] - sumc[q[tt - 1]]) >= (f[i] - f[q[tt]]) / (sumc[i] - sumc[q[tt]]) 删除不在凸包的点
while(hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (sumc[i] - sumc[q[tt]]) >= (f[i] - f[q[tt]]) * (sumc[q[tt]] - sumc[q[tt - 1]])) tt --;
q[++ tt] = i;
}
printf("%lld\n", f[n]);
return 0;
}
*/
int main() {
scanf("%d%d", &n, &s);
for(int i = 1, t, c; i <= n; i ++) {
scanf("%d%d", &t, &c);
sumt[i] = sumt[i - 1] + t;
sumc[i] = sumc[i - 1] + c;
}
int hh = 0, tt = 0;
q[0] = 0;
for(int i = 1; i <= n; i ++) {
int l = hh, r = tt;
while(l < r) { // 二分答案
int mid = (l + r) >> 1;
if((f[q[mid + 1]] - f[q[mid]]) <= (sumt[i] + s) * (sumc[q[mid + 1]] - sumc[q[mid]])) l = mid + 1;
else r = mid;
}
int j = q[l]; // 最优的点
f[i] = f[j] - (sumt[i] + s) * sumc[j] + sumt[i] * sumc[i] + s * sumc[n];
while(hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (sumc[i] - sumc[q[tt]]) >= (f[i] - f[q[tt]]) * (sumc[q[tt]] - sumc[q[tt - 1]])) tt --; // 删除不在凸包的点
q[++ tt] = i;
}
printf("%lld\n", f[n]);
return 0;
}
点击查看代码
#include <stdio.h> // f[i]为前i个任务答案,费用提前计算
#include <string.h> // f[i]=min{f[j]+(sumc[i]-sumc[j])sumt[i] + s*(sumc[n]-sumc[j])}
const int N = 3e5 + 5; // 斜率优化:设i的决策点为j ,则f[j]=()*sumc[j]+f[i]-常数
typedef long long LL; // 对于所有j有一个f[i],其中最小的就是真正的f[i] (线性规划)
typedef __int128_t LLL; // 相当于一条斜率固定的直线向上移动,移到第一个(sumc[j],f[j])
int n, s; LL f[N], sumt[N], sumc[N]; // 如果三个决策点j1<j2<j3,则j2有用<=>k(j1j2)<k(j2,j3)
int q[N]; // 使用单调队列维护满足条件的决策点(按照横坐标和斜率从小到大); // 在t非负的情况下,可以将斜率小的队头弹出
int main() { // 在t不一定非负的情况下,只能在单调队列中二分查找p左侧斜率小,右侧斜率大的位置(这就成单调栈了...)
scanf("%d%d", &n, &s);
for(int i = 1, t, c; i <= n; i ++)
scanf("%d%d", &t, &c), sumt[i] = sumt[i-1] + t, sumc[i] = sumc[i-1] + c;
int hh = 0, tt = 0;
for(int i = 1; i <= n; i ++) {
int l = hh, r = tt; // 二分查找
while(l < r) {
int mid = (l + r) >> 1;
if(mid < tt && f[q[mid+1]] - f[q[mid]] <= (sumt[i] + s) * LLL(sumc[q[mid+1]] - sumc[q[mid]])) l = mid + 1;
else r = mid;
}
f[i] = f[q[l]] - sumc[q[l]] * LL(sumt[i] + s) + sumc[i] * sumt[i] + s * sumc[n];
while(hh < tt && (f[q[tt]] - f[q[tt-1]]) * LLL(sumc[i] - sumc[q[tt]]) >= (f[i] - f[q[tt]]) * LLL(sumc[q[tt]] - sumc[q[tt-1]])) tt --;
q[++ tt] = i;
}
printf("%lld\n", f[n]);
return 0;
}