首先,如果一块土地被另一土地包含(比如5*10的土地包含4*6的土地),那么这块土地就不用考虑了,从数据中去掉。然后按土地的宽度排序,可以发现高度必然是递增的:
设f(i)为购买前i块土地所需最少费用,这个只需要枚举最后一组购买的土地设为j是哪些即可:f(i)= min(f(i),f(j - 1)+ w(j)* h(i)),w(i)是第i块土地的宽,h(i)则是高。
这个明显超时,这时就可以用斜率优化了。斜率优化的是利用比较两值的优异来实现快速转移的。设有两种选择分别为:从j到i,和从k到i,不妨设j < k。如果有:f(j - 1)+ w(j)* h(i) < f(k - 1)+ w(k)* h(i),也就是j比k要优。那么,通过转化得:
(f(j - 1)- f(k - 1))/ (w(j)- w(k)) < - h(i)。
通过观察,不等式左边的式子不就是求两点斜率的式子吗。有两个点(x1, y1) 、(x2, y2),那么它们连成的直线的斜率就是(y1 - y2)/(x1 - y1),那么,我们可以在图上对于每个i,以(w(i),f(i - 1))作为一个点:
可以发现点4、点2,,都是不可能作为最优答案的,为什么呢?假如点4作为最优答案,说明点5和点4的斜率是小于- h(i)的,那么点4与点3的斜率也是必然小于- h(i)的(不然点4就要“凸”出来了),也就是点3比点4优,假设不成立,所以像点4一样“凹”进去的点,是不可能作为最优答案的。
这样,我们只需要维护一个类似“凸包”的东西就可以了,每次的最优值就是队列中的头的编号。同时要注意,当开头两个点的斜率大于- h(i)时,则要把开头的点去掉。注意,-h(i)是单调下降的,所以这样的做法与单调队列有异曲同工之妙。
适用斜率优化的题目,一般动态规划的转移方程有乘除法,然后通过两点的优劣比较,化成斜率的公式。并且需要具有单调性,可以通过排序来寻找单调性。
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 50007;
int n;
struct Data
{
long long w, h;
bool operator < (Data const &o) const
{
return w > o.w || (w == o.w && h > o.h);
}
}d[N];
void Init()
{
scanf("%d\n", &n);
for (int i = 1; i <= n; i ++)
scanf("%I64d%I64d\n", &d[i].w, &d[i].h);
sort(d + 1, d + 1 + n);
int m = 1;
for (int i = 2; i <= n; i ++)
if (d[i].h > d[m].h)
d[++ m] = d[i];
n = m;
}
int Q[N];
long long f[N];
/* 比较 a / b 和 c / d 的大小,由于 a * d 最大达到 1e+18,所以要先判断整数部分。
当然也可以直接除,但可能会造成精度问题 */
inline int cmprc(long long a, long long b,
long long c, long long d)
{
long long t1 = a / b;
long long t2 = c / d;
if (t1 != t2)
if (t1 < t2) return -1;
else return 1;
a -= b * t1;
c -= d * t2;
t1 = a * d;
t2 = b * c;
if (t1 < t2) return -1;
else if (t1 > t2) return 1;
else return 0;
}
void Solve()
{
int lo = 1, hi = 1;
f[1] = d[1].w * d[1].h;
Q[1] = 1;
for (int i = 2; i <= n; i ++)
{
/* 当开头两个点的斜率大于- h(i)时,则要把开头的点去掉。*/
while (lo < hi &&
cmprc(f[Q[lo] - 1] - f[Q[lo + 1] - 1],
d[Q[lo]].w - d[Q[lo + 1]].w,
- d[i].h,
1) >= 0)
lo ++;
f[i] = min(f[i - 1] + d[i].w * d[i].h,
f[Q[lo] - 1] + d[Q[lo]].w * d[i].h);
/* 维护一个类似“凸包”的东西就行 */
while (lo < hi &&
cmprc(f[i - 1] - f[Q[hi] - 1],
d[i].w - d[Q[hi]].w,
f[Q[hi] - 1] - f[Q[hi - 1] - 1],
d[Q[hi]].w - d[Q[hi - 1]].w) >= 0)
hi --;
Q[++ hi] = i;
}
printf("%I64d\n", f[n]);
}
int main()
{
freopen("acquire.in", "r", stdin);
freopen("acquire.out", "w", stdout);
Init();
Solve();
return 0;
}