【斜率优化】任务安排

** 斜率优化**

  • 任务安排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  * 查询时 二分查找
                         * 插入时 将队尾不在凸包上的点全部删掉

任务安排1 P5785 [SDOI2012]

点击查看代码
#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;
}

任务安排2

点击查看代码

#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;
}

任务安排3

点击查看代码
#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;
}

`注:1.开long long 2.乘法要用double等或__int128_t 3.队列里初始一个0`
posted @ 2022-10-09 18:21  azzc  阅读(20)  评论(0编辑  收藏  举报