斜率优化dp 笔记

任务安排1

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

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

从时刻 00 开始,任务被分批加工,执行第 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

 f[i]表示选好前i个任务的最小值

关键点在于把每次开始的S时间造成的全部后续影响加到当前这次的操作中,这样就不用考虑之前启动了几次机器了,取消了后效性

虽然过程中的f[1~n-1]的设计的值与状态设计不一定相同但f[n]一定是相同的 

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'
 
using namespace std;
 
typedef pair<int, int> PII;
typedef long long ll;

const int N = 5010;

int n, S;
ll T[N], C[N], sum[N];
ll f[N];

int main()
{
	IOS
	cin >> n >> S;
	for(int i = 1; i <= n; i ++)cin >> T[i] >> C[i];
	for(int i = 1; i <= n; i ++)
	{
		sum[i] = sum[i - 1] + C[i];
		T[i] += T[i - 1];
	}
	
	for(int i = 1; i <= n; i ++)
	{
		f[i] = 2e18;
		for(int j = 1; j <= i; j ++)//[j, i]
		{
			//关键点在于把每次开始的S时间造成的全部后续影响加到当前这次的操作中
			//虽然过程中的f[1~n-1]的设计的值与状态设计不一定相同但f[n]一定是相同的 
			ll res = f[j - 1] + T[i] * (sum[i] - sum[j - 1]) + S * (sum[n] - sum[j - 1]);
			f[i] = min(f[i], res);
		}
	}
	cout << f[n];
	
	return 0;
}

任务安排2

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

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

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

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

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

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

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

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

输入格式

第一行包含整数 N。

第二行包含整数 S。

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

输出格式

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

数据范围

1≤N≤3×1e5,
1≤Ti,Ci≤512,
0≤S≤512

输入样例:
5
1
1 3
3 2
4 3
2 3
1 4
输出样例:
153

 除了数据范围其余和上一题一样

把j - 1看为 j可推出的公式:f[i]=f[j]+T[i]*(C[i]-C[j])+S*(C[n]-C[j])

可以发现当i固定时f[i]、C[i]、T[i]为定值,有两个未知量C[j]和f[j]

设f[j]为y,C[j]为x,整理一下式子

f[j]=(T[i] + S) * C[j] + f[i] - T[i]*C[i] - S*C[n]

约等于y = kx + b

可以发现截距b越小f[i]就越小,此时便来到了真正的斜率优化

找到下面最外围的凸包,找到第一个斜率大于k的那条边的左端点,此点就是在k斜率下到达y轴时最低的那个点(因为k>0° && k < 90°)

找这个凸包的办法:取出后两个点(x1,y1),(x2,y2)与当前点(x3,y3)比,如果第一个点和第二个点的斜率大于第一个点和第三个点的斜率就删掉最后一个点。循环往复。最后再加进这个点。

一般来说是用二分去找的

但这题还有点小性质,就是斜率k在不断变大,因此可以用类似双指针+单调队列的方式解决该问题(只是和单调队列很像)

任务安排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

这道题只能用二分了

过程会爆ll记得开int128

还有注意同一条线上的多个点只能存在最边缘的两个,中间的要全删掉

举个例子 

1、2两点都符合要求,但1更好,可能会错求成2

1、2两点都行,但要选1而不能选2

但我写的二分会找到最左边的点,所以不是这里的问题,思来想去与第二题还有一点不同,就是S和T下限从1变成了0,就会出现横坐标不变的情况,出现了斜率无限大的情况,所以出现了=的情况

类似的情况会出现,造成难以估量的bug,所以,凸包一定不要留线段中间的点!!!

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'

using namespace std;

typedef pair<int, int> PII;
typedef long long ll;

const int N = 300010;

int n, S;
ll T[N], C[N];
ll f[N];
int q[N];

int main()
{
    IOS
    cin >> n >> S;
    for(int i = 1; i <= n; i ++)cin >> T[i] >> C[i];
    for(int i = 1; i <= n; i ++)
    {
        C[i] += C[i - 1];
        T[i] += T[i - 1];
    }

    int hh = 0, tt = -1;
    q[++ tt] = 0;

    for(int i = 1; i <= n; i ++)
    {
        int l = hh, r = tt;
        while(l < r)
        {
        	int mid = l + r >> 1;
			ll x1 = C[q[mid]], y1 = f[q[mid]];
			ll x2 = C[q[mid + 1]], y2 = f[q[mid + 1]];
			if(y2 - y1 >= (T[i] + S) * (x2 - x1))r = mid;
			else l = mid + 1;
		} 

        int j = q[l];
        f[i] = f[j] + T[i] * (C[i] - C[j]) + S * (C[n] - C[j]);

        ll x3 = C[i], y3 = f[i];
        while(hh < tt)
        {
            ll x1 = C[q[tt - 1]], y1 = f[q[tt - 1]];
            ll x2 = C[q[tt]], y2 = f[q[tt]];

            //注意一定是>= !!!!!!
            if((__int128)(y2 - y1) * (x3 - x1) >= (__int128)(y3 - y1) * (x2 - x1))tt --;
            else break;
        }
        q[++ tt] = i;
    }
    cout << 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≤1e5,
1≤M≤1e5,
1≤P≤100,
1≤Di<1000,
1≤Hi≤N,
0≤Ti≤1e9

输入样例:
4 6 2
1 3 5
1 0
2 1
4 9
1 10
2 10
3 12
输出样例:
3

 

#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define endl '\n'

using namespace std;

typedef pair<int, int> PII;
typedef long long ll;

const int N = 110, M = 100010;

int n, m, p;
ll f[N][M];//前i个人,收回前j只猫
ll d[M], a[M];
int q[M];
ll sum[M];

ll gety(int i, int k)
{
	return f[i - 1][k] + sum[k];
}

int main()
{
	IOS
	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, t;
		cin >> h >> t;
		a[i] = t - d[h];
	}
	sort(a + 1, a + 1 + m);
	for(int i = 1; i <= m; i ++)sum[i] = sum[i - 1] + a[i];
	
	memset(f, 0x3f, sizeof f);
	//排除i个人带回0只小猫的代价为0
	for(int i = 0; i <= p; i ++)f[i][0] = 0;
	//派出0个人只有可能带回0只小猫 即f[0][0] = 0;已被包含
	
	for(int i = 1; i <= p; i ++)
	{
		int hh = 0, tt = -1;
		q[++ tt] = 0;
		
		for(int j = 1; j <= m; j ++)
		{
			//先把斜率小于a[j]的去掉
			while(hh < tt && gety(i, q[hh + 1]) - gety(i, q[hh]) < a[j] * (q[hh + 1] - q[hh]))hh ++;
			
			int k = q[hh];
			f[i][j] = f[i - 1][k] - sum[j] + sum[k] + j * a[j] - k * a[j];
			
			ll x3 = j, y3 = gety(i, j);
			while(hh < tt)
			{
				ll x1 = q[tt - 1], y1 = gety(i, q[tt - 1]);
				ll x2 = q[tt], y2 = gety(i, q[tt]);
				
				if((y2 - y1) * (x3 - x1) >= (y3 - y1) * (x2 - x1))tt --;
				else break;
			}
			q[++ tt] = j;
		}
	}
	cout << f[p][m];
	
	return 0;
}

  • 19
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: hdu 2829 Lawrence 斜率优化dp 这道题是一道经典的斜率优化dp题目,需要用到单调队列的思想。 题目大意是给定一个序列a,求出一个序列b,使得b[i]表示a[1]~a[i]中的最小值,且满足b[i] = min{b[j] + (i-j)*k},其中k为给定的常数。 我们可以将上式拆开,得到b[i] = min{b[j] - j*k} + i*k,即b[i] = i*k + min{b[j] - j*k},这个式子就是斜率优化dp的形式。 我们可以用单调队列来维护min{b[j] - j*k},具体思路如下: 1. 首先将第一个元素加入队列中。 2. 从第二个元素开始,我们需要将当前元素加入队列中,并且需要维护队列的单调性。 3. 维护单调性的方法是,我们从队列的末尾开始,将队列中所有大于当前元素的元素弹出,直到队列为空或者队列中最后一个元素小于当前元素为止。 4. 弹出元素的同时,我们需要计算它们对应的斜率,即(b[j]-j*k)/(j-i),并将这些斜率与当前元素的斜率比较,如果当前元素的斜率更小,则将当前元素加入队列中。 5. 最后队列中的第一个元素就是min{b[j] - j*k},我们将它加上i*k就得到了b[i]的值。 6. 重复以上步骤直到处理完所有元素。 具体实现可以参考下面的代码: ### 回答2: HDU 2829 Lawrence 斜率优化 DP 是一道经典的斜率优化 DP 题目,其思想是通过维护一个下凸包来优化 DP 算法。下面我们来具体分析一下这道题目。 首先,让我们看一下该题目的描述。题目给定一些木棒,要求我们将这些木棒割成一些给定长度,且要求每种长度的木棒的数量都是一样的,求最小的割枝次数。这是一个典型的背包问题,而且在此基础上还要求每种长度的木棒的数量相同,这就需要我们在状态设计上走一些弯路。 我们来看一下状态的定义。定义 $dp[i][j]$ 表示前 $i$ 个木棒中正好能割出 $j$ 根长度为 $c_i$ 的木棒的最小割枝次数。对于每个 $dp[i][j]$,我们可以分类讨论: 1. 不选当前的木棒,即 $dp[i][j]=dp[i-1][j]$; 2. 选当前的木棒,即 $dp[i][j-k]=dp[i-1][j-k]+k$,其中 $k$ 是 $j/c_i$ 的整数部分。 现在问题再次转化为我们需要在满足等量限制的情况下,求最小的割枝次数。可以看出,这是一个依赖于 $c_i$ 的限制。于是,我们可以通过斜率优化 DP 来解决这个问题。 我们来具体分析一下斜率优化 DP 算法的思路。我们首先来看一下动态规划的状态转移方程 $dp[i][j]=\min\{dp[i-1][k]+x_k(i,j)\}$。可以发现,$dp[i][j]$ 的最小值只与 $dp[i-1][k]$ 和 $x_k(i,j)$ 有关。其中,$x_k(i,j)$ 表示斜率,其值为 $dp[i-1][k]-k\times c_i+j\times c_i$。 接下来,我们需要维护一个下凸包,并通过斜率进行优化。我们具体分析一下该过程。假设我们当前要计算 $dp[i][j]$。首先,我们需要找到当前点 $(i,j)$ 在凸包上的位置,即斜率最小值的位置。然后,我们根据该位置的斜率计算 $dp[i][j]$ 的值。接下来,我们需要将当前点 $(i,j)$ 加入到下凸包上。 我们在加入点的时候需要注意几点。首先,我们需要将凸包中所有斜率比当前点小的点移除,直到该点能够加入到凸包中为止。其次,我们需要判断该点是否能够加入到凸包中。如果不能加入到凸包中,则直接舍弃。最后,我们需要保证凸包中斜率是单调递增的,这就需要在加入新的点之后进行上一步操作。 以上就是该题目的解题思路。需要注意的是,斜率优化 DP 算法并不是万能的,其使用情况需要根据具体的问题情况来确定。同时,该算法中需要维护一个下凸包,可能会增加一些算法的复杂度,建议和常规 DP 算法进行对比,选择最优的算法进行解题。 ### 回答3: 斜率优化DP是一种动态规划优化算法,其主要思路是通过对状态转移方程进行变形,提高算法的时间复杂度。HDU2829 Lawrence问题可以用斜率优化DP解决。 首先,我们需要了解原问题的含义。问题描述如下:有$n$个人在数轴上,第$i$个人的位置为$A_i$,每个人可以携带一定大小的行李,第$i$个人的行李重量为$B_i$,但是每个人只能帮助没有他们重量大的人搬行李。若第$i$个人搬运了第$j$个人的行李,那么第$i$个人会累加$C_{i,j}=\left|A_i-A_j\right|\cdot B_j$的体力消耗。求$m$个人帮助每个人搬运行李的最小体力消耗。 我们可以通过斜率优化DP解决这个问题。记$f_i$为到前$i$个人的最小体力消耗,那么状态转移方程为: $$f_i=\min_{j<i}\{f_j+abs(A_i-A_j)\cdot B_i\}$$ 如果直接使用该方程,时间复杂度为$O(n^2)$,如果$n=10^4$,则需要计算$10^8$次,运算时间极长。斜率优化DP通过一些数学推导将方程变形,将时间复杂度降低到$O(n)$,大大缩短了计算时间。 通过斜率优化DP的推导式子,我们可以得到转移方程为: $$f_i=\min_{j<i}\{f_j+slope(j,i)\}$$ 其中,$slope(j,i)$表示直线$j-i$的斜率。我们可以通过如下方式来求解$slope(j,i)$: $$slope(j,i)=\frac{f_i-f_j}{A_i-A_j}-B_i-B_j$$ 如果$slope(j,i)\leq slope(j,k)$,那么$j$一定不是最优,可以直接舍去,降低计算时间。该算法的时间复杂度为$O(n)$。 综上所述,斜率优化DP是一种动态规划优化算法,可以大大缩短计算时间。在处理类似HDU2829 Lawrence问题的时候,斜率优化DP可以很好地解决问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值