斜率优化DP——任务安排×4

一.任务安排1

洛谷P2365

P2365 任务安排

题目描述

\(n\) 个任务排成一个序列在一台机器上等待完成(顺序不得改变),这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。

从零时刻开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间为 \(t_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和(同一批任务将在同一时刻完成)。

每个任务的费用是它的完成时刻乘以一个费用系数 \(f_i\)。请确定一个分组方案,使得总费用最小。

输入格式

第一行一个正整数 \(n\)
第二行是一个整数 \(s\)

下面 \(n\) 行每行有一对数,分别为 \(t_i\)\(f_i\),表示第 \(i\) 个任务单独完成所需的时间是 \(t_i\) 及其费用系数 \(f_i\)

输出格式

一个数,最小的总费用。

输入输出样例 #1

输入 #1

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

输出 #1

153

说明/提示

【数据范围】
对于 \(100\%\) 的数据,\(1\le n \le 5000\)\(0 \le s \le 50\)\(1\le t_i,f_i \le 100\)

【样例解释】
如果分组方案是 \(\{1,2\},\{3\},\{4,5\}\),则完成时间分别为 \(\{5,5,10,14,14\}\),费用 \(C=15+10+30+42+56\),总费用就是 \(153\)

思路+AC代码

由于在计算时间时s的个数受前面所分批数的影响,所以最容易想到的是令dp[i][j]表示前i个任务分成j批完成的最小费用.

读题:同一批任务在同一时刻完成.也就是说如果该批任务是[l,r],这批的完成时刻为t,则所需要的时间为 s+t * \(\sum_l^r\)C.

则可以得到转移方程dp[i][j]=min{dp[k][j-1]+(s×j+sumt[i])×(sumc[i]-sumc[k])}(1\(\le\)k\(<\)i).

统计一下前缀和就行了.但是这样复杂度过高了,可以想一想怎么优化,省掉一维j.

因为j只在计算有多少个s的时候起作用,而每增加一批任务后面所有任务的时间就会多一个s,因此我们可以在计算当前dp的时候直接把后面的费用加上,在算当前费用的时候由于前面已经把s加过了,所以可以只统计t[k+1~i]的费用即可(说的有些模糊,这里需要好好理解).

于是乎我们的转移方程就变成了dp[i]=min{dp[k]+sumt[i]×(sumc[i]-sumc[k])+s×(sumc[n]-sumc[k])}(1\(\le\)k\(<\)i).

这道题就可以过掉了.

AC code
#include<bits/stdc++.h>
#define int long long
using namespace std;

inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch-'0');ch=getchar();}return x*f;}inline void write(int x){if(x<0)x*=-1,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');return;}inline int max(int x,int y){return (x<y)?y:x;}inline int min(int x,int y){return (x<y)?x:y;}

const int N=5010;
int n,s,t[N],f[N];
int sumt[N],sumf[N];
int dp[N];

signed main()
{
	n=read();s=read();
	for(int i=1;i<=n;i++)
	{
		t[i]=read();f[i]=read();
		sumt[i]=sumt[i-1]+t[i];
		sumf[i]=sumf[i-1]+f[i];
	}
	memset(dp,0x3f,sizeof(dp));
	dp[0]=0;
	
	for(int i=1;i<=n;i++)
		for(int l=1;l<=i;l++){
			dp[i]=min(dp[i],dp[l-1]+sumt[i]*(sumf[i]-sumf[l-1])+s*(sumf[n]-sumf[l-1]));
		}
	
	write(dp[n]);
	return 0;
}

二.任务安排2

洛谷P10979

P10979 任务安排 2

题目背景

本题是 P2365 强化版,是 P5785 弱化版,用于让学生循序渐进地了解斜率优化 DP。

题目描述

机器上有 \(n\) 个需要处理的任务,它们构成了一个序列。这些任务被标号为 \(1\)\(n\),因此序列的排列为 \(1 , 2 , 3 \cdots n\)。这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。从时刻 \(0\) 开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间是 \(T_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和。

注意,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 \(C_i\)

请确定一个分组方案,使得总费用最小。

输入格式

第一行一个整数 \(n\)。第二行一个整数 \(s\)

接下来 \(n\) 行,每行有一对整数,分别为 \(T_i\)\(C_i\),表示第 \(i\) 个任务单独完成所需的时间是 \(T_i\) 及其费用系数 \(C_i\)

输出格式

一行,一个整数,表示最小的总费用。

输入输出样例 #1

输入 #1

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

输出 #1

153

说明/提示

对于 \(100\%\) 数据,\(1 \le n \le 3 \times 10^5\)\(1 \le s \le 2^8\)\(1\le T_i \le 2^8\)\(0 \le C_i \le 2^8\)

思路+AC代码

不难发现唯一的改动就是范围,也就是说刚才的dp依然是可以优化的.

我们先把dp转移式变一下,把常数分离出来(相对于枚举k而言),dp[i]=min{ dp[k]-(s+sumt[i])×sumc[k] }+sumt[i]×sumc[i]+s×sumc[n]
(1\(\le\)k\(<\)i).

我们肯定要省去k的枚举.如果k\(_1\)优于k\(_2\),那么dp[k\(_1\)]-(s+sumt[i])×sumc[k\(_1\)]<dp[k\(_2\)]-(s+sumt[i])×sumc[k\(_2\)],移项得(dp[k\(_2\)]-dp[k\(_1\)])/(sumc[\(k_2\)]-sumc[\(k_1\)])>sumt[i]×s.

发现左边这一坨很像斜率的计算公式,这就是所谓的斜率优化DP了.我们可以把dp和sumc分别看为横纵坐标,设两点分别为P\(_1\)和P\(_2\),如果两点间斜率>sumt[i]×s,则P\(_1\)这个点更优,反之P\(_2\)更优.

假设有三个点A,B,C(横坐标从左到右).如果AB的斜率大于BC的斜率,那么就可以构成一个上凸壳.

我们设sumt[i]×s为斜率k\(_0\),k\(_0\)与上凸壳ABC的关系可分为三种:

1.k\(_A\)\(_B\)>k\(_B\)\(_C\)>k\(_0\),此时最优点为A
2.k\(_A\)\(_B\)>k\(_0\)>k\(_B\)\(_C\),此时最优点为A/C
3.k\(_0\)>k\(_A\)\(_B\)>k\(_B\)\(_C\),此时最优点为C

总之最优点不可能是B.所以在遇到上凸壳时,要把中间的点舍掉,正着说就是我们要维护一个下凸壳,可以使用单调队列来维护.

看代码悟一悟.

AC code
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl '\n'
#define lid (id<<1)
#define rid (id<<1|1)

const int N=300010;
int n,s,c[N],t[N];
int f[N],q[N];

inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch-'0');ch=getchar();}return x*f;}inline void write(int x){if(x<0)x*=-1,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');return;}inline int max(int x,int y){return (x<y)?y:x;}inline int min(int x,int y){return (x<y)?x:y;}

signed main()
{
	memset(f,0x3f,sizeof(f));
	n=read();s=read();
	for(int i=1;i<=n;i++)
	{
		t[i]=read();c[i]=read();
		t[i]+=t[i-1];c[i]+=c[i-1];
	}
	int l=1,r=1;
	f[0]=0;
	
	for(int i=1;i<=n;i++)
	{
		int k=s+t[i];
		while(l<r&&f[q[l+1]]-f[q[l]]<=k*(c[q[l+1]]-c[q[l]]))++l;
		f[i]=f[q[l]]-k*c[q[l]]+t[i]*c[i]+s*c[n];
		while(l<r&&(f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])>=(c[q[r]]-c[q[r-1]])*(f[i]-f[q[r]]))--r;
		q[++r]=i;
	}
	cout<<f[n];
	return 0;
}

三.任务安排3

洛谷P5785

P5785 [SDOI2012] 任务安排

题目描述

机器上有 \(n\) 个需要处理的任务,它们构成了一个序列。这些任务被标号为 \(1\)\(n\),因此序列的排列为 \(1 , 2 , 3 \cdots n\)。这 \(n\) 个任务被分成若干批,每批包含相邻的若干任务。从时刻 \(0\) 开始,这些任务被分批加工,第 \(i\) 个任务单独完成所需的时间是 \(T_i\)。在每批任务开始前,机器需要启动时间 \(s\),而完成这批任务所需的时间是各个任务需要时间的总和。

注意,同一批任务将在同一时刻完成。每个任务的费用是它的完成时刻乘以一个费用系数 \(C_i\)

请确定一个分组方案,使得总费用最小。

输入格式

第一行一个整数 \(n\)
第二行一个整数 \(s\)

接下来 \(n\) 行,每行有一对整数,分别为 \(T_i\)\(C_i\),表示第 \(i\) 个任务单独完成所需的时间是 \(T_i\) 及其费用系数 \(C_i\)

输出格式

一行,一个整数,表示最小的总费用。

输入输出样例 #1

输入 #1

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

输出 #1

153

说明/提示

对于 \(100\%\) 数据,\(1 \le n \le 3 \times 10^5\)\(1 \le s \le 2^8\),$ \left| T_i \right| \le 2^8$,\(0 \le C_i \le 2^8\)

思路+AC代码

注意到相比2来说,T可以取负值(离谱),也就是说在加点的时候横坐标不保证单调了,这可怎么办?

原理是不变的,只不过这次不能从单调队列队首开始找了,那就用二分咯!

AC code
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5+10;
inline int read(){int x=0,f=1;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch-'0');ch=getchar();}return x*f;}inline void write(int x){if(x<0)x*=-1,putchar('-');if(x>9)write(x/10);putchar(x%10+'0');return;}inline int min(int x,int y){return (x<y)?x:y;}

int n,s,t,c,l,r,sumt[N],sumc[N],f[N],q[N];

int ljy(int i,int k){
	if(l==r) return q[l];
	int L=1,R=r;
	while(L<R){
		int mid=(L+R)>>1;
		if(f[q[mid+1]]-f[q[mid]]<=k*(sumc[q[mid+1]]-sumc[q[mid]])) L=mid+1;
		else R=mid;
	}
	return q[L];
}

signed main()
{
	n=read();s=read();
	for(int i=1;i<=n;i++){
		t=read();c=read();
		sumt[i]=sumt[i-1]+t;
		sumc[i]=sumc[i-1]+c;
	}
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	l=1,r=1;
	
	for(int i=1;i<=n;i++)
	{
		int p=ljy(i,s+sumt[i]);
		f[i]=f[p]-(s+sumt[i])*sumc[p]+sumt[i]*sumc[i]+s*sumc[n];
		while(l<r&&(f[q[r]]-f[q[r-1]])*(sumc[i]-sumc[q[r]])>=(f[i]-f[q[r]])*(sumc[q[r]]-sumc[q[r-1]])) r--;
		q[++r]=i;
	}
	write(f[n]);
	return 0;
}
posted @ 2025-04-19 21:40  Crab2016  阅读(57)  评论(0)    收藏  举报