BZOJ:2726: [SDOI2012]任务安排(斜率优化)

该博客介绍了如何解决一个任务安排问题,其中目标是最小化费用。通过分析朴素方程和进阶版方程,提出了使用斜率优化的方法。博主详细解释了斜率优化的转移方程,并给出了实现该算法的三分搜索代码,但指出尝试使用CDQ方法并未通过所有测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目

你有n个任务和一台机器,第i个任务完成时间是s[i],带来的费用是它的完成时间*费用系数f[i]。
每次启动机器需要时间S,可以将几个任务分成一组,这样只需要启动一次机器,但是所有任务的完成时间都将被计算为最后一个任务完成的时间。
注意计算等待时间!!!
现在你需要使费用最小。
时间可以为负数
0 < N<=300000 0<=S<=2^8 -(2^8)<=Ti<=2^8 0<=Fi<=2^8

分析

朴素方程

f(i,j)表示将前i个元素分成j组的最小费用。
f(i,j)=min{ f(k,j-1) + ( s * j + sumT [i] )( sumF[i]-sumF[k]) | 0<=k< I }

sumT表示T的前缀和
sumF表示F的前缀和

时间复杂度O(n^3)
空间复杂度O(n^2)

会炸的

进阶版方程

假设我们分好了组:
第i组完成的时间是ai+i*s,第i组的费用系数和是bi。
Ans=(a1+s)*b1+(a2+2*s)*b2+…+(am+m*s)*bm
集中注意力辣!!
Ans=(a1*b1+a2*b2+…+am*bm)+(s*b1+s*2*b2+..s*m*bm)
=(a1*b1+a2*b2+…+am*bm)+s*( (b1+…+bm-1+bm) +…+(bm+1+bm)+(bm))
其中a其实是原T的前缀和,b那一堆可以处理成后缀和。
那么令sumT为T的前缀和,sumrF为F的后缀和,有转移方程如下:
F(i)=min{f(j)+sumT(i)*( sumrF(j+1)-sumrF(i+1) )+s*sumrF(j+1)|0<=j

斜率优化

define st sumT
define sf sumrF
令t1<=t2< I,则当t2为更优决策时,有:
F(t2)-F(t1)/(sf(t2+1)-sf(t1+1))>=-st[i]-s//sf(t2+1)-sf(t1+1))<0变号

则令y[i]=F(i),x[i]=sf(i+1)

x有单调性
所以维护下凸包即可,然后三分找答案。
注意在考虑的时候,由于X单调递减,所以所谓的往后一个元素实际上在图的位置上是往前一个元素,卡了好久哦。
这里最好用叉积(结合long doulbe),否则会炸掉

然后还可以用CDQ来做,这里我强行CDQ了一波(时间多了个log),随机数据挺快的,但是会被卡!!我的第一道CDQ啊啊啊啊,过都过不了。

代码

三分版

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double ld;
const int maxn=3e5+105;
const LL inf=1e18;
int n,s;
int t[maxn],f[maxn],st[maxn],sf[maxn];
LL d[maxn],x[maxn],y[maxn];
void Init()
{
    scanf("%d%d",&n,&s);
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&t[i],&f[i]);
        st[i]=st[i-1]+t[i];
    }
    for(int i=n;i>=1;i--)
        sf[i]=sf[i+1]+f[i];
}
int q[maxn],front,rear;
#define check(x) d[x]+1ll*st[i]*(sf[x+1]-sf[i+1])+1ll*s*sf[x+1]
LL sanfen(int l,int r,int i)
{
    LL ret=inf;
    int mid1,mid2;
    while(r-l>2)
    {
        mid1=l+(r-l)/3;
        mid2=r-(r-l)/3;
        if(check(q[mid1])<check(q[mid2]))
            r=mid2;
        else
            l=mid1;
    }
    for(int x=l;x<=r;x++)
        ret=min(ret,check(q[x]));
    return ret;
}
#define calc(a,b,c) (ld)(x[b]-x[a])*(y[c]-y[a])-(ld)(x[c]-x[a])*(y[b]-y[a])
void dp()
{
    front=rear=0;
    q[rear++]=0;
    x[0]=sf[1];
    for(int i=1;i<=n;i++)
    {
        d[i]=sanfen(front,rear-1,i);
        x[i]=sf[i+1],y[i]=d[i];
        while(rear-front>1 && calc(q[rear-2],q[rear-1],i)>=0)rear--;
        q[rear++]=i;
    }
    printf("%lld\n",d[n]);
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    Init();
    dp();
    return 0;
}

过不了的CDQ

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef long double ld;
const int maxn=3e5+105;
const LL inf=1e18;
int n,s;
int t[maxn],f[maxn],st[maxn],sf[maxn];
LL d[maxn],x[maxn],y[maxn];
namespace IStream{  
    const int L=1<<15;  
    char buffer[L],*S,*T;  
    inline char Get_Char()  
    {  
        if(S==T)  
        {  
            T=(S=buffer)+fread(buffer,1,L,stdin);  
            if(S==T) return EOF;  
        }  
        return *S++;  
    }  
    inline void Rd(int &re) 
    {  
        char c; 
        re=0;
        int k=1;
        for(c=Get_Char();(c<'0'||c>'9') && c!='-';c=Get_Char());  
        if(c=='-')c=Get_Char(),k=-1;
        while(c>='0'&&c<='9')  
            re=(re<<1)+(re<<3)+(c-'0'),c=Get_Char();  
        re*=k;
    } 
}
void Init()
{
    IStream::Rd(n);IStream::Rd(s);
    for(int i=1;i<=n;i++)
    {
        IStream::Rd(t[i]);IStream::Rd(f[i]);
        st[i]=st[i-1]+t[i];
    }
    for(int i=n;i>=1;i--)
        sf[i]=sf[i+1]+f[i];
}
int a[maxn],tmp[maxn];
int q[maxn],front,rear;
#define check(x) d[x]+1ll*st[i]*(sf[x+1]-sf[i+1])+1ll*s*sf[x+1]
LL sanfen(int l,int r,int i)
{
    LL ret=inf;
    int mid1,mid2;
    while(r-l>2)
    {
        mid1=l+(r-l)/3;
        mid2=r-(r-l)/3;
        if(check(q[mid1])<check(q[mid2]))
            r=mid2;
        else
            l=mid1;
    }
    for(int x=l;x<=r;x++)
        ret=min(ret,check(q[x]));
    return ret;
}
//a数组表示当前第i个位置的元素
#define calc(a,b,c) (ld)(x[b]-x[a])*(y[c]-y[a])-(ld)(x[c]-x[a])*(y[b]-y[a])
void CDQ(int l,int r)
{
    if(r-l==1)
    {
        x[a[l]]=sf[a[l]+1];
        y[a[l]]=d[a[l]];
        return;
    }
    int mid=(l+r)>>1;
    CDQ(l,mid);
    front=rear=0;
    for(int i=l;i<mid;i++)
    {
        while(rear-front>1 && calc(q[rear-2],q[rear-1],a[i])<=0)rear--;
        q[rear++]=a[i];
    }
    for(int i=mid;i<r;i++)
    {
        d[a[i]]=min(d[a[i]],sanfen(front,rear-1,a[i]));
    }
    CDQ(mid,r);
    int i=l,j=mid,k=0;
    while(i<mid && j<r)tmp[k++]=x[a[i]]<x[a[j]]?a[i++]:a[j++];
    while(i<mid)tmp[k++]=a[i++];
    while(j<r)tmp[k++]=a[j++];
    for(int i=0;i<k;i++)a[l+i]=tmp[i];
}
int main()
{
    //freopen("in.txt","r",stdin);
    //freopen("out1.txt","w",stdout);
    Init();
    for(int i=1;i<=n;i++)a[i]=i,d[i]=inf;
    CDQ(0,n+1);
    printf("%lld\n",d[n]);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值