题解 P2900 【[USACO08MAR]土地征用Land Acquisition】
用两种不一样的思路立体地理解斜率优化,你值得拥有。
题意分析
既然所有的土地都要买,那么我们可以考虑到,如果一块土地的宽和高(其实是蒟蒻把长方形立在了平面上)都比另一块要小,那么肯定是直接并购,这一块对答案没有任何贡献。
我们先把这些给去掉,具体做法可以是,按高为第一关键字,宽为第二关键字从大到小排序,然后上双指针扫一遍。
于是,剩下的就是一个高度递减、宽度递增的矩形序列。考虑怎样制定它们的并购方案会最优。显然如果要并购,一定要挑序列中的一段区间,这样贡献答案的就只有最左边矩形的高乘上最右边矩形的宽,中间的又是没有贡献了。
设
一看貌似是一个决策单调性优化的式子。然而。。。。。。
初中生都会的函数图像法
这种理解方法是在决策单调性优化DP的基础上应运而生的。
或者说,(在大多数情况下)斜率优化可以看作决策单调性优化的一种特殊情形。蒟蒻建议还是先入手决策单调性再来斜率优化吧。
蒟蒻的DP各种优化总结
蒟蒻之前写的一道经典(裸)决策单调性题的题解戳这里(Lightning Conductor)
对于每一个
真正有用的部分
这样的话,我们就用单调队列维护若干个斜率递减的函数。我们仍然需要按照决策单调性的做法,维护相邻两个决策直线间的临界值(交点)
这些决策不是直线吗?求两个直线的交点。。。。。。初中数学就教了,是
这样求出
高中生都会的线性规划法
这才是理解斜率优化的正宗方法,因为上面并没有充分体现对斜率的处理过程。
上面对两个相邻直线求
我们原来把决策当成直线,斜率为
现在要求解的问题又变成了什么样子呢?在平面上有若干个点,把
把式子变成
随便画一堆点就可以发现,无论直线以怎样的斜率向上靠,总有一些点永远都不会第一次与直线相交,也就是说这些决策是没用的。剩下的有用的决策点会构成一个凸包:(因为要画点所以换成了GeoGebra)
凸包的性质就是斜率递增/递减。在此题中,因为
这时候
实现
两种实现的代码长得都差不多,都要搞一个单调队列,都要求临界值/斜率。所以就放一个代码吧。。。
复杂度蒟蒻可不想来什么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;
}