P2365 任务安排 斜率优化dp入门(分组dp)

P2365 任务安排 斜率优化入门

题意

给出n个任务,必须按顺序完成,每个任务都有一个需要的时间和代价系数。可以把任务分批(就是把原序列分成多段),每一批内部的任务同样也是按顺序完成,但是最后计算代价的时候是该批中最后一个任务完成得时间*批中每一个任务得代价系数。同时,分批有一个代价时间S,表示启动机器的时间,也就是每分一批要时间S。

思路

刚开始没看懂题,感觉全部弄到一批就行了,其实不对,例如有两个任务,第一个时间为1,第二个时间为100000,代价均为一个比较大的值,S为1,那么肯定第一和第二分开代价比较小。
n<=5000 考虑一下怎么dp
基本套路是前i个分成j个的代价,这样的复杂度是\((n^3)\)肯定是过不了的
造成这个代价的最主要原因是记录了分成几组,如果不记录会造成后效性。实际上,我们可以考虑,假设分组增加了1,那么造成的代价是可以O(1)计算出来的,例如 设j<=i 如果我们将j---i分成一组,那么也就相当于 j----n的时间都要增加S,也就是增加了代价s*(sum[n]-sum[j-1]),这样就能避免后效性了,相当于我可以提前计算分组造成的代价,这个代价是分组必然造成的,而对后面的任务没有影响,假如在这个分组后又有分组,也是可以提前计算,这样就可以把复杂度优化到\(O(n^2)\)
我们设\(dp[i]\)表示1--i都完成了需要的最小代价,那么转移方程为
\(dp[i]=min(dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1]))\)

其实这题还可以更优化,我们把上述转移方程拿出来
\(dp[i]=dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1])\)

移动一下
\(dp[i]=dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1])\)

\(dp[j-1]=dp[i]-sumt[i]*(sumf[i]-sumf[j-1])-s*(sumf[n]-sumf[j-1])\)
化简一下
\(dp[j-1]=(s+sumt[i])*sumf[j-1]+dp[i]-sumt[i]*sumf[i]-s*sumf[n]\)
把dp[j-1]当成因变量,sumf[j-1]看作自变量,那么截距就是\(dp[i]-sumt[i]*sumf[i]\)要使dp[i]最小也就是使截距最小

可以看出斜率k=(s+sumt[i])是单调递增的
找到dp[i]的最大值就当于把斜率为k的直线向上平移,在碰到的第一个(dp[j-1],sumt[j-1])点取得最小值。

什么时候能碰到,假设碰到的点为x1 那么设(x1,x0)斜率为k0和(x1,x2)斜率为k1 那么取得最小值的斜率关系有\(k0=<k=<k1\)所以只要维护一个下凸壳就行,每次取能取第一个大于斜率(s+sumf[i])点(当点不足得时候只能取那一个点) ,并且维护一个下凸壳即可

\(n^2\)代码

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define F first
#define S second
#define mkp make_pair
#define pii pair<int,int>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=1e6+10;
const int mod=1e9+7;
int dp[maxn],sumt[maxn],sumf[maxn];
int main(){
	int n,s;
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;i++){
		int x,y;scanf("%d%d",&x,&y);
		sumt[i]=sumt[i-1]+x;
		sumf[i]=sumf[i-1]+y;
	}
	for(int i=1;i<=n;i++)dp[i]=inf;
	dp[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=i;j++){
			dp[i]=min(dp[i],dp[j-1]+sumt[i]*(sumf[i]-sumf[j-1])+s*(sumf[n]-sumf[j-1]));
		}
	}
	cout<<dp[n];
	return 0;
}

斜率优化代码


#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define F first
#define S second
#define mkp make_pair
#define pii pair<int,int>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int maxn=1e6+10;
const int mod=1e9+7;
int dp[maxn],sumt[maxn],sumf[maxn],q[maxn];
int main(){
	int n,s;
	scanf("%d%d",&n,&s);
	for(int i=1;i<=n;i++){
		int x,y;scanf("%d%d",&x,&y);
		sumt[i]=sumt[i-1]+x;
		sumf[i]=sumf[i-1]+y;
	}
	for(int i=1;i<=n;i++)dp[i]=inf;
	dp[0]=0;
	int l,r=1;
	r=l=1;
	for(int i=1;i<=n;i++){
		while(l<r&&(dp[q[l+1]]-dp[q[l]])<=(s+sumt[i])*(sumf[q[l+1]]-sumf[q[l]]))l++;
		dp[i]=dp[q[l]]+sumt[i]*(sumf[i]-sumf[q[l]])+s*(sumf[n]-sumf[q[l]]);
		while(l<r&&(dp[q[r]]-dp[q[r-1]])*(sumf[i]-sumf[q[r]])>=(dp[i]-dp[q[r]])*(sumf[q[r]]-sumf[q[r-1]]))r--;
		q[++r]=i;
	}
	cout<<dp[n];
	return 0;
}
posted @ 2020-04-06 23:26  tttttttttrx  阅读(193)  评论(0编辑  收藏  举报