题目描述
农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足(1 <= 宽 <= 1,000,000; 1 <= 长 <= 1,000,000).
每块土地的价格是它的面积,但FJ可以同时购买多快土地. 这些土地的价格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换. 如果FJ买一块3x5的地和一块5x3的地,则他需要付5x5=25.
FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费. 他需要你帮助他找到最小的经费.
题解
这道题中,我们首先可以排除一些无用的点。例如,当一个点的长和宽都小于另外的某一个点时,这个点就可以排除。具体实现方法是:
- 将数组按照长为第一关键字,宽为第二关键字进行排序。
- 维护一个单调栈,将小于当前节点宽的点都弹出栈;由于按照长来排序,所以可以保证弹出的点长和宽都小于当前点。
- 然后我们就可以得到一个宽单调递减的栈,其长则单调不下降。
那么一段区间最答案的贡献是由左右端点造成的,因此答案一定是连续的一部分。
我们设 f [ i ] f[i] f[i]表示到了第 i i i块土地的最小经费。则有 f [ i ] = m i n ( f [ j ] + x [ i ] ∗ y [ j + 1 ] ) f[i]=min(f[j]+x[i]*y[j+1]) f[i]=min(f[j]+x[i]∗y[j+1]).
因为含有i和j的乘积项,因此我们可以大力展开然后斜率优化。
#include <bits/stdc++.h>
#define LL long long
using namespace std;
LL n;
struct node
{
LL x,y;
friend bool operator < (node p1,node p2)
{
if (p1.x == p2.x) return p1.y < p2.y;
else return p1.x < p2.x;
}
} ;
LL top = 0;
LL f[100000];
LL x[100000];
LL y[100000];
LL q[100000];
LL st[100000];
node a[100000];
void init(void)
{
cin>>n;
for (LL i=1;i<=n;++i)
cin>>a[i].x>>a[i].y;
sort(a+1,a+n+1);
for (LL i=1;i<=n;++i)
x[i] = a[i].x, y[i] = a[i].y;
return;
}
void work(void)
{
for (LL i=1;i<=n;++i)
{
while (y[i] >= y[st[top]] && top > 0) top --;
st[++top] = i;
}
for (LL i=1;i<=top;++i) x[i] = x[st[i]];
for (LL i=1;i<=top;++i) y[i] = y[st[i]];
return;
}
double k(LL p1,LL p2)
{
//p1 > p2
//x(p1) < x(p2)
#define X(i) (y[i+1])
#define Y(i) (-f[i])
double dty = Y(p2)-Y(p1);
double dtx = X(p2)-X(p1);
return 1.0*dty/dtx;
}
void dp(void)
{
n = top;
LL h = 1, t = 1;
for (LL i=1;i<=n;++i)
{
while (h < t && k(q[h+1],q[h]) < x[i]) h ++;
f[i] = f[q[h]]+x[i]*y[q[h]+1];
while (h < t && k(i,q[t]) < k(i,q[t-1])) t --;
q[++t] = i;
}
cout<<f[n]<<endl;
return;
}
int main(void)
{
freopen("acquire.in","r",stdin);
freopen("acquire.out","w",stdout);
init();
work();
dp();
return 0;
}