洛谷P2900 [USACO08MAR]土地征用Land Acquisition(动态规划,斜率优化,决策单调性,线性规划,单调队列)

洛谷题目传送门html

用两种不同的思路立体地理解斜率优化,你值得拥有。函数

题意分析

既然全部的土地都要买,那么咱们能够考虑到,若是一块土地的宽和高(实际上是蒟蒻把长方形立在了平面上)都比另外一块要小,那么确定是直接并购,这一块对答案没有任何贡献。优化

咱们先把这些给去掉,具体作法能够是,按高为第一关键字,宽为第二关键字从大到小排序,而后上双指针扫一遍。spa

因而,剩下的就是一个高度递减、宽度递增的矩形序列。考虑怎样制定它们的并购方案会最优。显然若是要并购,必定要挑序列中的一段区间,这样贡献答案的就只有最左边矩形的高乘上最右边矩形的宽,中间的又是没有贡献了。3d

\(f_i\)为前\(i\)个矩形的最小花费,\(w\)为宽,\(h\)为高,直接写出一个\(O(n^2)\)的方程指针

\[f_i=\min\limits_{j=1}^i\{f_{j-1}+w_ih_j\}\]code

一看貌似是一个决策单调性优化的式子。然而。。。。。。htm

初中生都会的函数图像法

这种理解方法是在决策单调性优化DP的基础上应运而生的。blog

或者说,(在大多数状况下)斜率优化能够看做决策单调性优化的一种特殊情形。蒟蒻建议仍是先入手决策单调性再来斜率优化吧。排序

蒟蒻的DP各类优化总结

蒟蒻以前写的一道经典()决策单调性题的题解戳这里(Lightning Conductor)

对于每个\(f_{j-1}+w_ih_j\),咱们均可以把它视为一个直线\(l_j:y=ax+b\),其中\(a=h_j,b=f_{j-1}\)。对于每个\(i\),咱们就是须要求出全部\(j\le i\)的直线的\(x\)\(w_i\)时最小的一个\(y\)值。仍然用KmPlot画一个咱们须要维护的全部直线的样子,它们应该知足斜率依次递减。

\(l_1:y=2x;\)

\(l_2:y=x+1;\)

\(l_3:y=\frac x 2+3;\)

\(l_4:y=\frac x 6+5.\)

真正有用的部分

这样的话,咱们就用单调队列维护若干个斜率递减的函数。咱们仍然须要按照决策单调性的作法,维护相邻两个决策直线间的临界值(交点)\(k\)。难道还要维护决策二分栈,对每一个临界值都二分么?

这些决策不是直线吗?求两个直线的交点。。。。。。初中数学就教了,是\(O(1)\)的。也就是对两个相邻决策直线\(l_1,l_2\),咱们求\(\frac{b_2-b_1}{a_1-a_2}\)。其它过程跟决策单调性是如出一辙的。直线入队前,若是队尾不知足斜率递增则出队。求\(f_i\)以前,先把队首临界值\(\le w_i\)的决策出队,那么如今队首就是最优决策了。

这样求出\(f_n\)只须要\(O(n)\)的时间。

高中生都会的线性规划法

这才是理解斜率优化的正宗方法,由于上面并无充分体现对斜率的处理过程。

上面对两个相邻直线求\(\frac{b_2-b_1}{a_1-a_2}\),看起来有点像求什么东西。

咱们原来把决策当成直线,斜率为\(a\),截距为\(b\)。如今咱们换一下。把决策\(f_{j-1}+w_ih_j\)看做一个点\(p_j(x,y)\),其中\(x=-h_j,y=f_{j-1}\)

如今要求解的问题又变成了什么样子呢?在平面上有若干个点,把\(f_i\)当作目标函数\(z\),咱们须要找到\(f_i=w_ih_j+f_{j-1}\)\(z=-w_ix+y\)的最小值。这不是个线性规划么?

把式子变成\(y=w_ix+f_i\),如今就让咱们来最小化截距\(f_i\)。手(nao)动(dong)模拟一下,咱们如今正在拿着一个斜率为\(w_i\)的直线,从下往上移动,当第一次通过某个决策点的时候,直线的在\(y\)轴上的截距就是咱们要求的目标函数\(f_i\)的最小值了。

随便画一堆点就能够发现,不管直线以怎样的斜率向上靠,总有一些点永远都不会第一次与直线相交,也就是说这些决策是没用的。剩下的有用的决策点会构成一个凸包:(由于要画点因此换成了GeoGebra)

凸包的性质就是斜率递增/递减。在此题中,由于\(w\)递增,因此咱们的单调队列中存的是若干个点知足\(x\)递增(\(h\)递减),\(y\)递增,并且相邻两个点的斜率也递增。这和原序列的顺序是同向的。假设队尾下标为\(t\),当须要在队尾加入一个新的决策点时,咱们可能会遇到这样的状况:

这时候\(p_t\)已经不优了,咱们把它出队,如此直到知足斜率递增为止,\(p_i\)就能够入队了。和上面那种理解方法的写法差很少,求相邻两个点造成的直线斜率而后比一下大小。队首的处理跟上面那种理解方法的写法也差很少,若是队首与后一个的斜率小于\(w_i\)就出队。最后的队首依然是最优解。

实现

两种实现的代码长得都差很少,都要搞一个单调队列,都要求临界值/斜率。因此就放一个代码吧。。。

复杂度\(O(n\log n)\),瓶颈居然在sort上?!蒟蒻可不想来什么wys排序

#include<cstdio>
#include<algorithm>
#define RG register
#define R RG int
#define G c=getchar()
#define Calc(i,j) (f[j-1]-f[i-1])/(a[i].h-a[j].h)
//method1:求出临界值
//method2:求出斜率
using namespace std;
const int N=1e5+9;
int q[N];
double f[N],k[N];
//method1:k_i为决策直线q_i与q_i+1的临界值(交点)
//method2:k_i为决策点q_i与q_i+1所连成直线的斜率
struct Land{
    int w,h;//结构体排序
    inline bool operator<(RG Land&x)const{
        return h>x.h||(h==x.h&&w>x.w);
    }
}a[N];
inline int in(){
    RG char G;
    while(c<'-')G;
    R x=c&15;G;
    while(c>'-')x=x*10+(c&15),G;
    return x;
}
int main(){
    R n=in(),i,h,t;
    for(i=1;i<=n;++i)
        a[i].w=in(),a[i].h=in();
    sort(a+1,a+n+1);
    for(h=i=1;i<=n;++i)//双指针扫描去除无用矩形
        if(a[h].w<a[i].w)a[++h]=a[i];
    n=h;
    for(h=i=1,t=0;i<=n;++i){
        while(h<t&&k[t-1]>=Calc(q[t],i))--t;//维护临界值/斜率单调
        k[t]=Calc(q[t],i);q[++t]=i;//加入决策直线/决策点
        while(h<t&&k[h]<=a[i].w)++h;//弹出已经不优的决策
        f[i]=(double)a[q[h]].h*a[i].w+f[q[h]-1];//求出最优解
    }
    printf("%.0lf\n",f[n]);
    return 0;
}
相关文章
相关标签/搜索