学习笔记--斜率优化

上篇文章讲了凸包,那么这篇文章我们来讲讲跟凸包联系比较大的斜率优化:
所谓斜率优化指的是对于一系列我们不能够用各种鬼畜的数据结构来维护,达到降低时间复杂度的目的的鬼畜题。。。但是DP方程有一定特殊性,满足决策单调之类的。。。我们就可以采用这种数形结合的方法来乱搞了。

那么来看一道例题(JZOJ 1132):

F国际航空公司在世界范围有n个国际机场。第i 个国际机场到中心机场的距离为di,i=1,…,n。从国际机场j到国际机场i的飞行费用为w(i,j)=s+(dj-di)2,s为地面加油费用。从任何国际机场飞往中心机场的飞机可以在任一国际机场加油后继续飞行。飞机加油问题要求确定从距中心机场最远的国际机场飞到中心机场的最少费用。
对于给定的n个国际机场到中心机场的距离d1,d2,……,dn,以及地面加油费用s,编程计算从距中心机场最远的国际机场飞到中心机场的最少费用。

输入:
第一行有2个整数n和s,表示有n(n <= 400000)个国际机场(不包括中心机场),地面加油费用s。接下来的1 行中每行有n个整数d1,d2, ……,dn,表示给定的n个国际机场到中心机场的距离。

样例输入: 样例输出:
5 10 64
1 3 6 7 10

这一题乍一看非常简单,嗯,对 di 排序后随手敲出DP方程,设状态 f[i] 表示到第i个国际机场的最小费用,然后瞎列个转移方程:
f[i]=min(f[i],f[j]+(d[j]d[i])2+s)

嗯嗯,欣喜的以为问题解决了,然后瞄到数据范围n<=400000。。。flag不要太早立啊,于是我们转头想优化,取最小值容易想到单调队列取最小,然而 (d[j]d[i])2 不是常数就不能怎么做,这时候就需要用到斜率优化了。。

我们对方程做如下变形:
f[i]=f[j]+d[j]2+d[i]2+2d[i]d[j]+s
则有 2d[i]d[j]+f[i]=f[j]+d[i]2+d[j]2+s
这个式子就很像我们数学中二维平面的斜截式啦 kx+b=y
kx=2d[i]d[j],y=f[j]+d[i]2+d[j]2+s,b=f[i]
那么我们就可以想我们要使 f[i] 最小,那么它在y轴的截距就要尽量小,我们对于每一个
f[i] f[j] 里选择一个作为决策点,那么对于直线 y=kx+b 我们将它向上平移,
碰到的第一个点就是最优点了(此时 b 最小)。。我们可以求出每个可能决策点之间的连线的斜率,找到斜率和它最接近的。
那么我们就需要对于两个平面点k<j化成斜率式,大约就是这样:
(f[k]+d[k]2(f[j]+d[j]2))/(d[k]d[j])
嗯,然后很显然的由于我们要找的斜率 2d[i] 单调上升,所以维护一个下凸壳啊!!!
那些肯定不能成为决策点的点要删去嘛,由于下凸壳,斜率单升,所以开个单调队列维护二元组 (i,j) 就ok了,于是乎复杂度 O(n) 了.

代码:

# include<cstdio>
# include<algorithm>
using namespace std;
const int N = 4e5 + 10;
int q[N],d[N],f[N],g[N];
int i,n,h,t,s;
double xie(int k,int j)
{
    return (g[k] - g[j]) * 1.0 / (d[k] - d[j]);
}
int main()
{
    scanf("%d%d",&n,&s);
    n++;
    for (i = 2;i <= n; i++)
        scanf("%d",d + i);
    sort(d + 1,d + n + 1);
    g[n] = d[n] * d[n];
    h = 1,t = 1; q[h] = n;
    for (i = n - 1;i >= 1; i--)
    {
        if (d[i] == d[i + 1]) { f[i] = f[i + 1]; continue; }
        while (h < t && xie(q[h],q[h + 1]) >= 2.0 * d[i]) h++;
        f[i] = f[q[h]] + (d[q[h]] - d[i]) * (d[q[h]] - d[i]) + s;
        g[i] = f[i] + d[i] * d[i];
        while (h < t && xie(q[t - 1],q[t]) < xie(q[t],i)) t--;
        q[++t] = i;
    }
    printf("%d",f[1]);
    return 0;
}

偷懒把 f[i]+d[i]2 直接设成 g[i]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值