斜率优化DP


任务安排1

有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻 0 开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci

请为机器规划一个分组方案,使得总费用最小。

输入格式
第一行包含整数 N。

第二行包含整数 S。

接下来 N 行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci

输出格式
输出一个整数,表示最小总费用。

数据范围
1 ≤ N ≤ 5000,
0 ≤ S ≤ 50,
1 ≤ Ti,Ci ≤ 100

输入样例:

5
1
1 3
3 2
4 3
2 3
1 4

输出样例:

153

分析

对于该类序列,考虑用 动态规划 进行子问题划分求解再合并集合

如何进行状态表示:

状态表示-集合 fi,j:考虑前 i 个 任务,且当前第 i 个 任务 是第 j 个 批次 的 最后一个任务 的 方案

状态表示-属性 fi,j:方案的贡献 最小 Min

转移方程:
在这里插入图片描述
两维状态 套一个 决策变量,时间复杂度为 O(n3)
这里 蓝书 上用了一个 经典的优化思想: 费用提前计算 思想

题目中并没有说,我们 至多 只能够把任务分成 k 批次,可是在 DP 中我们却人为的引入了参数 j,仅仅只是因为我们在计算 当前状态 时,需要知道 S 对于 该批次 j 的 贡献:Scostj=S×j
然而,比起 j 对 fi 的 影响,我们 实际上 关心的是 S 对 fi 的 影响

j 是为了求出 S 对 fi 的影响,而被我们额外引入的参数

若当前为 [l,r] 的 任务 开一个新 批次,那么 该批次 机器的 启动时间 S 会 影响 的 任务 有 [l,n]
那么,我们不妨用 费用提前计算 思想,把该段 [l,n] 的 S 费用 直接累加到 当前状态 fi 上 计算

这样,省掉了 一维 状态表示。一维状态+一个决策变量,时间复杂度 为 O(n2)
在这里插入图片描述

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 5010;
int n, s;
int sc[N], st[N];
LL f[N]; // 前i个任务分别执行的最小费用 
int main()
{
	cin >> n >> s;
	for (int i = 1; i <= n; i ++ )
	{
		cin >> st[i] >> sc[i];
		st[i] += st[i - 1];
		sc[i] += sc[i - 1];
	}
	memset(f, 0x3f, sizeof f);
	f[0] = 0;
	// 以i结尾 以j+1开头 是同一批次 
	for (int i = 1; i <= n; i ++ ) 
		for (int j = 0; j < i; j ++ )  
			f[i] = min(f[i], f[j] + (sc[i] - sc[j]) * (LL)st[i] + (LL)s * (sc[n] - sc[j]));
	printf("%lld\n", f[n]);
	return 0;
} 

笔记学习:
作者:彩色铅笔
链接:https://www.acwing.com/solution/content/68062/
来源:AcWing


任务安排2

有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻 0 开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci

请为机器规划一个分组方案,使得总费用最小。

输入格式
第一行包含整数 N。

第二行包含整数 S。

接下来 N 行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci

输出格式
输出一个整数,表示最小总费用。

数据范围
1 ≤ N ≤ 3×105,
1 ≤ Ti,Ci ≤ 512,
0 ≤ S ≤ 512

输入样例:

5
1
1 3
3 2
4 3
2 3
1 4

输出样例:

153

在前缀中求一个极值,可以用斜率优化DP维护凸包

#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 300010;
int n, s;
LL c[N], t[N];
LL f[N];
int q[N];
int main()
{
	cin >> n >> s;
	for (int i = 1; i <= n; i ++ ) 
	{
		cin >> t[i] >> c[i];
		t[i] += t[i - 1];
		c[i] += c[i - 1];
	}
	int hh = 0, tt = 0;
	q[0] = 0;
	for (int i = 1; i <= n; i ++ )
	{
		while (hh < tt && (f[q[hh + 1]] - f[q[hh]]) <= (t[i] + s) * (c[q[hh + 1]] - c[q[hh]])) hh ++ ;
		int j = q[hh];
		f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
		while (hh < tt && (f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt -- ;
		q[ ++ tt] = i;
	}
	printf("%lld\n", f[n]);
	return 0;
}

任务安排3

有 N 个任务排成一个序列在一台机器上等待执行,它们的顺序不得改变。

机器会把这 N 个任务分成若干批,每一批包含连续的若干个任务。

从时刻 0 开始,任务被分批加工,执行第 i 个任务所需的时间是 Ti

另外,在每批任务开始前,机器需要 S 的启动时间,故执行一批任务所需的时间是启动时间 S 加上每个任务所需时间之和。

一个任务执行后,将在机器中稍作等待,直至该批任务全部执行完毕。

也就是说,同一批任务将在同一时刻完成。

每个任务的费用是它的完成时刻乘以一个费用系数 Ci

请为机器规划一个分组方案,使得总费用最小。

输入格式
第一行包含两个整数 N 和 S。

接下来 N 行每行有一对整数,分别为 Ti 和 Ci,表示第 i 个任务单独完成所需的时间 Ti 及其费用系数 Ci

输出格式
输出一个整数,表示最小总费用。

数据范围
1 ≤ N ≤ 3×105,
0 ≤ S,Ci ≤ 512,
−512 ≤ Ti ≤ 512

输入样例:

5 1
1 3
3 2
4 3
2 3
1 4

输出样例:

153

本题 相较于 上一题 的不同之处在于:−512 ≤ ti ≤ 512
该限制使得 ti 的 前缀和 sti 不再是 单调递增 的了

#include <iostream>
using namespace std;
typedef long long LL;
const int N = 300010;
int n, s;
LL t[N], c[N];
LL f[N];
int q[N];
int main()
{
	cin >> n >> s;
	for (int i = 1; i <= n; i ++ )
	{
		cin >> t[i] >> c[i];
		t[i] += t[i - 1];
		c[i] += c[i - 1];
	}
	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]] > (t[i] + s) * (c[q[mid + 1]] - c[q[mid]])) r = mid;
			else l = mid + 1;
		}
		int j = q[r];
		f[i] = f[j] - (t[i] + s) * c[j] + t[i] * c[i] + s * c[n];
		while (hh < tt && (double)(f[q[tt]] - f[q[tt - 1]]) * (c[i] - c[q[tt - 1]]) >= (double)(f[i] - f[q[tt - 1]]) * (c[q[tt]] - c[q[tt - 1]])) tt -- ;
		q[ ++ tt] = i;
	}
	printf("%lld\n", f[n]);
	return 0;
}

运输小猫

小 S 是农场主,他养了 M 只猫,雇了 P 位饲养员。

农场中有一条笔直的路,路边有 N 座山,从 1 到 N 编号。

第 i 座山与第 i−1 座山之间的距离为 Di

饲养员都住在 1 号山。

有一天,猫出去玩。

第 i 只猫去 Hi 号山玩,玩到时刻 Ti 停止,然后在原地等饲养员来接。

饲养员们必须回收所有的猫。

每个饲养员沿着路从 1 号山走到 N 号山,把各座山上已经在等待的猫全部接走。

饲养员在路上行走需要时间,速度为 1 米/单位时间。

饲养员在每座山上接猫的时间可以忽略,可以携带的猫的数量为无穷大。

例如有两座相距为 1 的山,一只猫在 2 号山玩,玩到时刻 3 开始等待。

如果饲养员从 1 号山在时刻 2 或 3 出发,那么他可以接到猫,猫的等待时间为 0 或 1。

而如果他于时刻 1 出发,那么他将于时刻 2 经过 2 号山,不能接到当时仍在玩的猫。

你的任务是规划每个饲养员从 1 号山出发的时间,使得所有猫等待时间的总和尽量小。

饲养员出发的时间可以为负。

输入格式
第一行包含三个整数 N,M,P。

第二行包含 n−1 个整数,D2,D3,…,DN

接下来 M 行,每行包含两个整数 Hi 和 Ti

输出格式
输出一个整数,表示所有猫等待时间的总和的最小值。

数据范围
2 ≤ N ≤ 105,
1 ≤ M ≤ 105,
1 ≤ P ≤ 100,
1 ≤ Di < 1000,
1 ≤ Hi ≤ N,
0 ≤ Ti ≤ 109

输入样例:

4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12

输出样例:

3
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 100010, M = 100010, P = 110;
int n, m, p;
LL d[N], t[N], a[N], s[N];
LL f[P][M];
int q[M];
LL get_y(int k, int j)
{
	return f[j - 1][k] + s[k];
}
int main()
{
	cin >> n >> m >> p;
	for (int i = 2; i <= n; i ++ )
	{
		cin >> d[i];
		d[i] += d[i - 1];
	}
	for (int i = 1; i <= m; i ++ )
	{
		int h;
		cin >> h >> t[i];
		a[i] = t[i] - d[h];
 	}
 	sort(a + 1, a + m + 1);
 	for (int i = 1; i <= m; i ++ ) s[i] = s[i - 1] + a[i];
 	memset(f, 0x3f, sizeof f);
 	for (int i = 0; i <= p; i ++ ) f[i][0] = 0;
 	for (int j = 1; j <= p; j ++ )
 	{
		int hh = 0, tt = 0;
		q[0] = 0;
		for (int i = 1; i <= m; i ++ )
		{
			while (hh < tt && (get_y(q[hh + 1], j) - get_y(q[hh], j)) <= a[i] * (q[hh + 1] - q[hh])) hh ++ ;
			int k = q[hh];
			f[j][i] = f[j - 1][k] - a[i] * k + s[k] + a[i] * i - s[i];
			while (hh < tt && (get_y(q[tt], j) - get_y(q[tt - 1], j)) * 
			(i - q[tt]) >= (get_y(i, j) - get_y(q[tt], j)) * (q[tt] - q[tt - 1])) tt -- ;
			q[ ++ tt] = i;
		}
	}
	printf("%lld\n", f[p][m]);
	return 0;
} 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值