『土地征用 Land Acquisition 斜率优化DP』
<更新提示>
<第一次更新> 斜率优化DP的综合运用,对斜率优化的新理解。
详细介绍见『玩具装箱TOY 斜率优化DP』
<正文>
土地征用 Land Acquisition(USACO08MAR)#
Description#
Farmer John is considering buying more land for the farm and has his eye on N (1 <= N <= 50,000) additional rectangular plots, each with integer dimensions (1 <= width_i <= 1,000,000; 1 <= length_i <= 1,000,000).
If FJ wants to buy a single piece of land, the cost is $1/square unit, but savings are available for large purchases. He can buy any number of plots of land for a price in dollars that is the width of the widest plot times the length of the longest plot. Of course, land plots cannot be rotated, i.e., if Farmer John buys a 3x5 plot and a 5x3 plot in a group, he will pay 5x5=25.
FJ wants to grow his farm as much as possible and desires all the plots of land. Being both clever and frugal, it dawns on him that he can purchase the land in successive groups, cleverly minimizing the total cost by grouping various plots that have advantageous width or length values.
Given the number of plots for sale and the dimensions of each, determine the minimum amount for which Farmer John can purchase all
约翰准备扩大他的农场,眼前他正在考虑购买N块长方形的土地。如果约翰单买一块土 地,价格就是土地的面积。但他可以选择并购一组土地,并购的价格为这些土地中最大的长 乘以最大的宽。比如约翰并购一块3 × 5和一块5 × 3的土地,他只需要支付5 × 5 = 25元, 比单买合算。 约翰希望买下所有的土地。他发现,将这些土地分成不同的小组来并购可以节省经费。 给定每份土地的尺寸,请你帮助他计算购买所有土地所需的最小费用。
Input Format#
-
Line 1: A single integer: N
-
Lines 2..N+1: Line i+1 describes plot i with two space-separated integers: width_i and length_i
Output Format#
- Line 1: The minimum amount necessary to buy all the plots.
Sample Input#
4
100 1
15 15
20 5
1 100
Sample Output#
500
解析#
这是一道USACO的题,相比之前的玩具装箱,难度较大,我们通过这道题来更透彻地理解斜率优化DP。
显然,这不是一道明显的DP题,我们需要先做一些转换。有一个很明显的贪心,如果一块土地P的长和宽都大于另一块土地Q的话,那么显然Q我们是可以无视的,应为我们在购买P时,可以顺便购买Q,不会带来任何额外的花费。
STEP 1: 解决预备问题#
- 为了建立合适的DP模型,我们先要处理题面中的一些其他的问题。
这里我们使用栈来解决无用土地这个问题。
先将所有土地存入一个结构体中,然后就可以以长度l为第一关键字,宽度w为第二关键字对土地进行排序。排序完成后,我们需要将土地一一加入栈。
在加入一块土地时,我们需要检查:栈顶的土地的宽度是否小于等于这块土地的宽度,由排序可知,栈顶的土地的长度一定小于等于这块土地的长度,如果栈顶的土地的宽度也小于等于这块土地的宽度的话,我们就可以判定当前栈顶的土地是一块无用土地。那么,可以将栈顶土地直接弹出。
最后,栈中剩下的土地就是有效的土地。我们将其重新取出,并重新以长度l为第一关键字,宽度w为第二关键字对土地进行排序。
STEP 2: 建立DP模型#
- 在处理完其他问题后,我们需要建立朴素DP模型。
这时候,如果我们将所得的有效土地视为一个个矩形,排序后,放在平面直角坐标系中应满足:长度x随着下标的增大而增大,高度y随着下标的增大而减小,如下图所示。
设fi代表购买了前i块土地的最小花费,利用排序后的性质,那么我们就可以建立状态转移方程:
STEP 3: 建立斜率函数#
- 得到了DP方程后,观察得知,方程形如fi=min/max{ai+bj+kcidj+fj},即:方程中除了含有fj以及单独和i,j有关的项外,还含有既和i有关,又和j有关的项。对于这种方程,我们考虑斜率优化。首先,我们需要利用状态转移方程建立斜率函数。
加入我们找到了最优的fj,那么由方程可得:
通常,我们视fj为y,既和i有关,又和j有关的项中,视和i有关的部分为k,和j有关的部分为x,方程中的其余项为b。那么就可以得到斜率函数y=kx+b。
这里,出现了一种特殊情况:既和i有关,又和j有关的项是负数,那么我们通常将负号看做是与y有关的项的。
那么结果就是:视fj为y,视−yj+1为x,视xi为k,视fi为b,得到y=kx+b。
STEP 4: 分析单调性及凸包的性质#
- 得到斜率函数后,我们需要通过图像分析其性质,完成代码实现。
首先,我们得出函数的性质:
1.过定点Pj(−yj+1,fj)
2.斜率为xi
再通过我们之前得出的性质:土地高度y随着下标的增大而减小,以及花费fj满足随着j的增大而增大,可以画出如下图像。
满足斜率为xi的直线自上向下移动,由于我们需要截距fi最小,当他碰到第一个Pj时,我们便得到了最优决策j。
注意到xi满足单调上升,P点围成的凸包的两两节点之间的斜率也满足单调递增,就可以得到如下性质。
1.slope(Pj,Pj+1)>xi且slope(Pj−1,Pj)<xi时,j为最优决策,否则j永远不会成为最优决策
2.slope(Px,Py)>slope(Px,Py′),决策y′优于y
确认其具有如上性质后,单调队列维护即可。
STEP 5: 代码实现#
- 确立作法后,我们需要代码实现
注意事项
1.斜率函数中的斜率满足单调递增(递减)时,可以使用单调队列维护,转移均摊时间复杂度O(1)
2.斜率函数中的斜率不满足单调递增(递减),但满足两两节点之间的斜率单调递增的凸包存在时(即性质2有效时),可以使用单调栈维护,二分查找找到最优决策,转移均摊时间复杂度O(log2n)
3.slope函数的返回值为double类型
4.注意是否需要开长整型变量(longlong)
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=50000+80;
struct earth{long long x,y;}a[N],Stack[N],d[N];
long long n,top=0,f[N],q[N*2],head=1,tail=1;
inline bool cmp(earth p1,earth p2){if(p1.x==p2.x)return p1.y<p2.y;else return p1.x<p2.x;}
inline void input(void)
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&a[i].x,&a[i].y);
}
inline void init(void)
{
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++)
{
while(top&&Stack[top].y<=a[i].y)top--;
Stack[++top]=a[i];
}
for(int i=1;i<=top;i++)
{
d[i]=Stack[i];
}
sort(d+1,d+top+1,cmp);
}
inline double slope(long long p,long long q){return (f[q]-f[p])*1.0/(d[p+1].y-d[q+1].y);}
inline void dp(void)
{
f[0]=0;
for(int i=1;i<=top;i++)
{
while(head<tail&&slope(q[head],q[head+1])<d[i].x)head++;
f[i]=f[q[head]]+d[q[head]+1].y*d[i].x;
while(head<tail&&slope(q[tail-1],q[tail])>slope(q[tail-1],i))tail--;
q[++tail]=i;
}
}
int main(void)
{
input();
init();
dp();
printf("%lld\n",f[top]);
}
<后记>
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ASP.NET Core 模型验证消息的本地化新姿势
· 对象命名为何需要避免'-er'和'-or'后缀
· SQL Server如何跟踪自动统计信息更新?
· “你见过凌晨四点的洛杉矶吗?”--《我们为什么要睡觉》
· 编程神器Trae:当我用上后,才知道自己的创造力被低估了多少
· C# 从零开始使用Layui.Wpf库开发WPF客户端
· C#/.NET/.NET Core技术前沿周刊 | 第 31 期(2025年3.17-3.23)
· 接口重试的7种常用方案!