洛谷 P5785 [SDOI2012] 任务安排(DP 斜率优化+二分查找)

这题代码调了我好久。。。

题目传送门:洛谷 P5785

转移方程的推导

设 f(i) 表示完成前 i 个任务的最小花费。

我们可以很容易得到转移方程:

f(i)=\min(f(j)+(C_i-C_j) \times T_i+(C_n-C_j) \times S)

其中 j<iC_i 为任务花费的前缀和,T_i 为完成任务所需时间的前缀和。

但是这样的时间复杂度是 O(n^2) 的,那是会超时的好吧。

所以我们得考虑该怎么优化。

DP 优化的过程

将上面的转移方程展开(可以不管取 \min

f(i)=f(j)+C_i \times T_i - C_j \times T_i + C_n \times S - C_j \times S

把 i 看作常数,把 j 看作未知数,将只含 j 的式子移到等号左边,其余移项到等号右边。

聪明的你通过手算后可以得到这个式子:

f(j)-C_j \times S= C_j \times T_i + f(i)-C_i \times T_i - C_n \times S

然后你就会发现 C_j \times T_i 这个项既与 i 有关又与 j 有关,所以这是不能用单调队列优化的。

但是聪明的你会看着这个式子有点眼熟。

觉得它有点像 y=kx+b

所以,我们可以考虑斜率优化

在这道题中,

y=f(j)-C_j \times S

k=T_i

x=C_j

b=f(i)-C_i \times T_i - C_n \times S

注意:

在这道题目中,\left | T_i \right | \leq 2^8,所以我们的斜率 k 不是递增的,所以我们就用二分查找凸包上的决策点即可。

然后直接套斜率优化的模板即可。

代码

如果直接算斜率可能会有精度丢失,所以这里是交叉相乘比较大小。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,s;
int t[300001],c[300001];
int a[300001],b[300001];
int f[300001];
int q[300001];
int dx(int i,int j)
{
    int x1=c[i];
    int x2=c[j];
    return x1-x2;
}
int dy(int i,int j)
{
    int y1=f[i]-c[i]*s;
    int y2=f[j]-c[j]*s;
    return y1-y2;
}
int head=1,tail=0;
int erfen(int x)
{
    if(head==tail)return q[head];
    int l=head,r=tail,ans;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(dy(q[mid],q[mid-1])<=t[x]*dx(q[mid],q[mid-1]))
            ans=mid,l=mid+1;
        else
            r=mid-1;
    }
    return q[ans];
}
main(){
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin>>n>>s;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i]>>b[i];
        t[i]=t[i-1]+a[i];
        c[i]=c[i-1]+b[i];
    }
    for(int i=1;i<=n;i++)
    {
        while(head<tail&&dy(q[tail],q[tail-1])*dx(i-1,q[tail])>=dx(q[tail],q[tail-1])*dy(i-1,q[tail]))
        {
            tail--;
        }
        q[++tail]=i-1;
        int j=erfen(i);
        f[i]=f[j]+(c[i]-c[j])*t[i]+(c[n]-c[j])*s;
    }
    cout<<f[n]<<endl;
    return 0;
}

  • 6
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值