[BZOJ2726][SDOI2012]任务安排(斜率优化dp+cdq分治)

74 篇文章 0 订阅

题目:

我是超链接

题解:

Emmm首先一眼dp了吧。

O(n3)

很蒻的博主就设计了一个浅显的dp[i][j]表示前i个分成j组的最小费用, O(n3) 而且看上去根本没有办法优化的样子?!

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,s,T[10005],F[10005],dp[5005] [5005],ans;
int main()
{
    scanf("%d%d",&n,&s);
    for (int i=1;i<=n;i++) 
    {
        scanf("%d%d",&T[i],&F[i]);
        T[i]+=T[i-1]; F[i]+=F[i-1];
    }
    memset(dp,0x7f,sizeof(dp)); ans=dp[0][0]; dp[0][0]=0;
    for (int i=1;i<=n;i++) dp[i][1]=(s+T[i])*F[i];
    for (int i=2;i<=n;i++)
      for (int k=1;k<i;k++)
        for (int j=2;j<=k+1;j++) 
          dp[i][j]=min(dp[i][j],dp[k][j-1]+(F[i]-F[k])*(j*s+T[i]));
    for (int i=1;i<=n;i++) ans=min(ans,dp[n][i]);
    printf("%d",ans);
}

O(n2)

不行啊,上面这一坨看上去根本没法优化,起码需要一个 O(n2) 的方法啊
从头来过,f(i)表示前i个的最小花费… 但是时间有后效性啊,这个后效性主要就在这个s上,因为你不知道前面分了多少组啊
那么我们把s单独抽出来考虑,假设我们将整个序列划分成了m段,每一段的F的和依次是 p1,p2..pm ,那么s带来的花费就是
sp1+2sp2+3sp3+mspm
=s(p1+2p2+3p3++mpm)
观察前面的那个式子,系数是等差的,其实稍微转化一下就是另一种形式:
(p1+p2+p3++pm)+(p2+p3++pm)+(p3++pm)++(pm)
F的后缀和?!
这样的话,我们就非常完美地将s所带来的影响消除了

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,s,T[10005],F[10005],dp[10005],ans;
int main()
{
    scanf("%d%d",&n,&s);
    for (int i=1;i<=n;i++) scanf("%d%d",&T[i],&F[i]),T[i]+=T[i-1];
    for (int i=n-1;i>=1;i--) F[i]+=F[i+1];
    memset(dp,0x7f,sizeof(dp)); dp[0]=0; 
    for (int i=1;i<=n;i++)
      for (int j=0;j<i;j++) 
        dp[i]=min(dp[i],dp[j]+s*F[j+1]+(F[j+1]-F[i+1])*T[i]);
    printf("%d",dp[n]);
}

O(nlogn)

上面的 f[i]=min(f[j]+F[j+1](s+Ti)F[i+1]T[i])
T如果是不小于0的话随便看看就应该知道单调队列就可以做
我一看SDOI题目数据范围,-(2^8)<=Ti<=2^8,你的T都能小于0了你咋不上天呐
这一波画画柿子就可以斜率优化dp做了吧
f[j]+sF[j+1]=TiF[j+1]+F[i+1]Ti+f[i]
这就是斜率为-Ti,截距为F[i+1]*Ti+f[i],当截距最小时,由于F[i+1]*Ti为定值,所以f[i]最小,维护一个下凸壳
因为要枚举j找一个最优值,我们可以把(F[j+1],f[j]+s*F[j+1])当做一个点,这不就跟刚才的题目一样啦
当然也可以用splay维护凸包(挖坑

卡常数技巧:
把不用longlong的开成int!
用快读

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
using namespace std;
const int INF=1e9;
const int N=300005;
struct hh{LL y,d;int x,k,id;}Q[N],jl[N];
int n,stack[N],T[N],F[N],s;LL dp[N];
int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
    return x*f;
}
double getk(hh a,hh b)
{
    if (a.x==b.x) return -INF;
    return ((double)(a.y-b.y)/(double)(a.x-b.x));
}
void merge(int l,int r)
{
    int mid=(l+r)>>1,t1=l,t2=mid+1;
    for (int i=l;i<=r;i++)
      if ((t1<=mid && Q[t1].x<Q[t2].x) || t2>r) jl[i]=Q[t1++];
      else jl[i]=Q[t2++];
    for (int i=l;i<=r;i++) Q[i]=jl[i];
}
void cdq(int l,int r)
{
    if (l==r){Q[l].x=F[l+1]; Q[l].y=dp[l]+(LL)s*F[l+1]; return;}
    int mid=(l+r)>>1,t1=l,t2=mid+1,top=0;   
    for (int i=l;i<=r;i++)
      if (Q[i].id<=mid) jl[t1++]=Q[i];
      else jl[t2++]=Q[i];
    for (int i=l;i<=r;i++) Q[i]=jl[i];
    //按照时间分开,此时右边已经是想要的形式,左边会在cdq分治里完成  
    cdq(l,mid);
    for (int i=l;i<=mid;i++)//维护一个下凸壳 
    {
        while (top>1 && getk(Q[stack[top]],Q[i])<=getk(Q[stack[top-1]],Q[stack[top]])) top--;
        stack[++top]=i;
    }
    int now=1;
    for (int i=mid+1;i<=r;i++)
    {
        while (now<top && getk(Q[stack[now]],Q[stack[now+1]])<jl[i].k) now++;
        int j=stack[now];
        dp[jl[i].id]=min(dp[jl[i].id],Q[j].y-(LL)Q[j].x*jl[i].k-jl[i].d);
    }
    cdq(mid+1,r); merge(l,r);
}
int cmp(hh a,hh b){return a.k<b.k;}
int main() 
{
    n=read(); s=read();
    for (int i=1;i<=n;i++) T[i]=read(),F[i]=read(),T[i]+=T[i-1];
    for (int i=n;i>=0;i--) F[i]+=F[i+1];
    for (int i=0;i<=n;i++) Q[i].k=-T[i],Q[i].id=i,Q[i].d=(LL)F[i+1]*T[i];
    sort(Q,Q+n+1,cmp);
    memset(dp,0x7f,sizeof(dp)); dp[0]=0;
    cdq(0,n);
    printf("%lld",dp[n]);
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值