斜率优化入门题:任务安排$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
",f[n]);
    return 0;
}
View Code

任务安排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
",f[n]);
    return 0;
}
View Code

任务安排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
",f[n]);
    return 0;
}
View Code
光伴随的阴影
原文地址:https://www.cnblogs.com/forward777/p/11250841.html