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