斜率优化入门题:任务安排$123$ : )
任务安排1
1≤N≤5000,1≤S≤50,1≤Ti,Ci≤100
朴素做法:
$f[i][j]=min\ {f[k][j-1]+(S*j+sumT)*(sumC[i]-sumC[k])}$
复杂度为$O(N^{3})$
优化:
"费用提前计算思想"
发现每重新启动一次,由于启动而增加的总费用是可以直接计算的,为$sumC[n]-sumC[k]$
$f[i]=min\ f[j]+sumT[i]*(sumC[i]-sumC[j])+S*(sumC[n]-sumC[j])\ $
所以可以去掉j的一维,复杂度降为$O(N^{2})$
#include<iostream> #include<cstdio> #define Rg register #define il inline #define go(i,a,b) for(Rg int i=a;i<=b;i++) #define yes(i,a,b) for(Rg int i=a;i>=b;i--) #define inf 2100000000 using namespace std; int n,s,t[5010],c[5010],f[5010]; il int read() { int x=0,y=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*y; } int main() { n=read(),s=read(); go(i,1,n)t[i]=t[i-1]+read(),c[i]=c[i-1]+read(),f[i]=inf; go(i,1,n)//i+1~j go(j,0,i-1) f[i]=min(f[i],f[j]+t[i]*(c[i]-c[j])+s*(c[n]-c[j])); printf("%d\n",f[n]); return 0; }
任务安排2
1≤N≤3*105,1≤S,Ti,Ci≤512
进一步优化
把$min$函数去掉,$f[j]$,$sumC[j]$看做变量
$f[i]=f[j]-(sumT[i]+S)*sumC[j]+sumT[i]*sumC[i]+S*sumC[n]$
$f[j]=(sumT[i]+S)*sumC[j]-sumT[i]*sumC[i]-S*sumC[n]+f[i]$
在以$sumC[j]$为横坐标,$f[j]$为纵坐标的平面直角坐标系中,它是一条以$sumT[i]+S$为斜率,$(f[i]-sumT[i]*sumC[i]-S*sumC[n])$为截距的直线,每个决策$j$都对应着坐标系中的一个点,由于 $(-sumT[i]*sumC[i]-S*sumC[n])$ 的值一定,所以当直线截距最小时,$f[i]$取得最小
于是我们只要维护一个相邻两点线段斜率单调递增的下凸壳即可.答案就是比$sumT[i]+S$大的最小斜率所对应的截距,另外,由于$sumT[i]+S$也是递增的,所以对于每一个$i$斜率比$sumT[i]+S$小的可以直接弹出队列,因为它以后也不会成为答案
$Attention!$单调队列中的第一个元素不是第一个$j$对应的点,而是$(0,0)!$
#include<iostream> #include<cstdio> #define il inline #define Rg register #define go(i,a,b) for(Rg int i=a;i<=b;i++) #define yes(i,a,b) for(Rg int i=a;i>=b;i++) #define inf 2100000000 using namespace std; il int read() { int x=0,y=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*y; } const int N=300010; int n,s,c[N],t[N],f[N],q[N]; int main() { n=read(),s=read(); go(i,1,n)t[i]=t[i-1]+read(),c[i]=c[i-1]+read(); int l=1,r=1;//q[1]=(0,0)!! go(i,1,n) { while(l<r && (f[q[l+1]]-f[q[l]])<=(s+t[i])*(c[q[l+1]]-c[q[l]]))l++; f[i]=f[q[l]]-(s+t[i])*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]])>=(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]]))r--; q[++r]=i; } printf("%d\n",f[n]); return 0; }
任务安排3
1≤N≤3*105,0≤S,Ci≤512,-512≤Ti≤512
$T$有可能为负,所以直线的斜率$sumT[i]+S$不是单调递增的,所以当前答案左边的点以后也有可能成为答案,也就是说我们要维护整个下凸壳.这样显然队首不一定是最优答案,要在队列中二分查找一个点$p$,使得它左边的线段斜率小于等于$sumT[i]+S$,右边的线段斜率大于$sumT[i]+S$. $p$就是最优解.
#include<iostream> #include<cstdio> #define il inline #define Rg register #define go(i,a,b) for(Rg int i=a;i<=b;i++) #define yes(i,a,b) for(Rg int i=a;i>=b;i++) #define inf 2100000000 #define int long long using namespace std; il int read() { int x=0,y=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')y=-1;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-'0';c=getchar();} return x*y; } const int N=300010; int n,s,c[N],t[N],q[N],f[N]; il int find(int l,int r,int x) { int ret; while(l<=r) { int mid=(l+r)>>1; if((f[q[mid]]-f[q[mid-1]])<=1LL*x*(c[q[mid]]-c[q[mid-1]]))ret=mid,l=mid+1; else r=mid-1; } return q[ret]; } main() { n=read(),s=read(); go(i,1,n)t[i]=t[i-1]+read(),c[i]=c[i-1]+read(); int l=1,r=1;//q[1]=(0,0)!! go(i,1,n) { //while(l<r && (f[q[l+1]]-f[q[l]])<=1LL*(s+t[i])*(c[q[l+1]]-c[q[l]]))l++; int p=find(l,r,s+t[i]); f[i]=f[p]-1LL*(s+t[i])*c[p]+1LL*t[i]*c[i]+1LL*s*c[n]; while(l<r && 1LL*(f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])>=1LL*(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]]))r--; q[++r]=i; } printf("%lld\n",f[n]); return 0; }