BZOJ 1597 土地购买 斜率优化

首先,我们看啊,这道题是把线性序列分段的问题,于是显然需要用DP
(^o^)/YES!
于是,f[i]就代表购买前i块土地所需要的最小费用
那么我们就来思考方程,但是发现可能需要我们先来排一排序,搞一搞就出来方程了
于是我们把length升序排列,放到一个队列中
这时我们发现有这样一种现象
存在land[i].length>=land[j].length&&land[i].width>=land[j].width那么j完全没有存在的必要(j表示我好伤心~~(>_<)~~),但是不管你伤不伤心我们都要删去j
这样之后,我们就会发现排完序后length按照升序排列,width一定按照降序排列(你自己YY吧)
那么显然DP方程就是f[i]=min(f[i],f[j]+land[j+1].width*land[i].length)
(⊙v⊙)嗯,看起来很对的样子
(^o^)/YES!
等等,但是你有没有发现这是一个n^2的算法,而n最大为50000,(⊙v⊙)嗯……炸了>o<
看起来nlgn还是可以接受的
那么我们就需要优化一下啦
O(∩_∩)O哈哈~
锵锵锵,斜率优化闪亮登场

f[i]=f[j]+land[j+1].width*land[i].length
-land[i].length*land[j+1].width+f[i]=f[j]
-land[i].length就代表斜率,f[i]代表截距,那么(land[j+1].width,f[j])就可以看作关于i的点集中的j点
因为我们要求最小值,也就是求最小截距,于是我们就有这样一条截距为-land[i].length的直线,然后有一堆点,如果一个一个枚举就,就,炸了 >o<……
所以呢,我们要维护一个单调队列,使得斜率单调递增
然后我们看这样一种现象
设在当前状态f[i]时,从f[j]转移比从f[k]转移更优,那么f[j]+land[j+1].width*land[i].length < f[k]+land[k+1].width*land[i].length
变一变形,搞一搞,就出现了一个神奇的东西
land[i].length>(f[j]-f[k])/(land[k+1].width-land[j+1].width)
如果j和k满足上述形式,那么无视k就好啦
然后取出队首两个点,发现这两个点的斜率比i斜率小,那么队首这个点一定不能满足我们的要求,所以弹出队首即可(队首表示我很光荣的就牺牲了~(≧▽≦)/~)

g[k, j] = (dp[k] - dp[j]) / (land[j+1].width - land[k+1].width )
顺便说一下,k比j小

那么我们可以总结一下上面推出来的式子:
根据上面有:
g[k, j] >= land[i].length 决策k不比决策j差
同理可证: g[k, j] < land[i].length 决策j比决策k优

进而我们发现这么一个问题,当i < c < b < a时,如果有g[a, b] > g[b, c],那么b永远都不会成为计算dp[i]时的决策点。

证明:
如果g[a, b] > g[b, c],那么我们可以分两个方面考虑g[a, b]与land[i].length 的关系:
(1)如果g[a, b] >= land[i].length ,那么决策a不会比决策b差,也就说决策b不可能是决策点
(2)如果g[a, b] < land[i].length ,那么由于g[a, b] > g[b, c],那么g[b, c] < land[i].length ,那么决策c要比决策b好,所以b还不能作为决策点
根据上面的结论和一些特性,我们可以考虑维护一个斜率的队列来优化整个DP过程:
于是有以下一些东东:
(1)假设a, b, c依次是队列尾部的元素,那么我们就要考虑g[a, b]是否大于g[b, c],如果g[a, b] > g[b, c],那么可以肯定b一定不会是决策点,所以我们可以从队列中将b去掉,然后依次向前推,直到找到一个队列元素少于3个或者g[a, b] <= g[b, c]的点才停止。
(2)对于i的更新,一定是队列头部的决策点最好,所以O(1)即可转移。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define LL long long
using namespace std;
const int maxn=50000+10;
struct field{
    LL length,width;
}land[maxn],land2[maxn];
LL n,f[maxn],q[maxn];
bool cmp(field a,field b){
    return a.length<b.length||a.length==b.length&&a.width>b.width;//使长单调上升
}
int main(){
    cin>>n;
    for(LL i=1;i<=n;i++)
        scanf("%d%d",&land[i].length,&land[i].width);
    sort(land+1,land+n+1,cmp);
    LL tail=1;
    land2[1].length=land[1].length,land2[1].width=land[1].width;
    for(LL i=2;i<=n;i++){
        while(tail&&land[i].width>land2[tail].width)
            tail--;
        land2[++tail].length=land[i].length,land2[tail].width=land[i].width;
    }
    memset(f,0x3f,sizeof(f));
    LL hd=1,ta=1;
    q[1]=0,f[0]=0;
    for(int i=1;i<=tail;i++){
        while((hd<ta)&&(land2[i].length*(land2[q[hd]+1].width-land2[q[hd+1]+1].width)>f[q[hd+1]]-f[q[hd]]))
            hd++;   
        f[i]=f[q[hd]]+land2[q[hd]+1].width*land2[i].length;
        while((hd<ta)&&(f[i]-f[q[ta-1]])*(land2[q[ta]+1].width-land2[i+1].width)>=(f[i]-f[q[ta]])*(land2[q[ta-1]+1].width-land2[i+1].width))
            ta--;
        q[++ta]=i;
    }
    cout<<f[tail]<<endl;
    return 0;
}

by >o< neighthorn

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值