每块土地的长和宽分别用 l l 和数组表示。
因为一组土地购买的价格 = = 最大的长 最大的宽,所以对于一块土地 x x ,如果存在一块土地,满足 l[y]>=l[x] l [ y ] >= l [ x ] 且 h[y]>=h[x] h [ y ] >= h [ x ] ,那么它只要把土地 x x 和土地合为一组,最大的长可以不取 l[x] l [ x ] ,最大的宽可以不取 h[x] h [ x ] ,所以土地 x x 的存在与否对答案是没有任何影响的。
因此,可以把所有土地按长度从小到大排序,对于长度相同的按宽度从小到大排序。然后维护一个栈,将排序后的土地一个一个加入,每次加入之前,先把当前栈中宽小于等于它的土地全部删除(因为这些土地长肯定小于等于它),再加入它。最后留在栈里的土地就是有存在必要的土地。
可以发现,此时留下的土地的宽度是从大到小排好的。所有还有一个结论:此时在最优决策下,每一组土地都是连续的一段。为什么呢?举个例子:假设这时有三个土地, y y ,,是连续的三块土地,那么有: l[x]<l[y]<l[z] l [ x ] < l [ y ] < l [ z ] 且 h[x]>h[y]>h[z] h [ x ] > h [ y ] > h [ z ] 。考虑把 x x 和分为一组, y y 单独一组,要花费的费用。如果把 y y 也分进, z z 那一组呢?只要的费用,省去了 y y 单独一组的费用。
因此,考虑。设 f[i] f [ i ] 表示前 i i 个土地的最小费用。易得转移方程:,然而这是 O(n2) O ( n 2 ) 的,过不去。
考虑优化。求出 f[j] f [ j ] 之后,设 a=h[j+1] a = h [ j + 1 ] , b=f[j] b = f [ j ] ,就可以表示一条直线: y=ax+b y = a x + b ,求 f[i] f [ i ] 等价于在已经表示出的所有直线中,找一个当 x=l[i] x = l [ i ] 时 y y 最大的。注意到是递增的,而 h[i] h [ i ] 是递减的。如图1:
可以发现,一旦 l[i]>p l [ i ] > p 的横坐标,较优的永远是直线 p2 p 2 , p1 p 1 已经没有用了。因此维护一个单调队列,对于每一个 i i ,比较单调队列队头两个生成的直线,只要当 x=l[i] x = l [ i ] 时,队头第一个不如队头第二个优,就把队头第一个删除了,直到队头第一个比第二个优。
然而还有这样一种情况,如图2:
按照之前的那种做法,过点 A A 之前,都会选择直线最优,但是在过 B B 点之后,已经不如 p3 p 3 优了。所以每次把直线 i i 加入队列作为以后备选的之前,先判断队尾两条直线和直线 i i 是否有如上图的关系,有的话删除队尾第一条,直到不存在上图情况,加入直线。
判断即: p1 p 1 与 p2 p 2 交点横坐标是否大于 p1 p 1 与 p3 p 3 交点横坐标。需要用到除法,可以通过移项时它变为乘法,避免因交点横坐标不为整数带来的精度问题。
总结一下就是,先将土地排序,去掉没有存在必要的土地,然后进行 dp d p ,先删除队头不优的,删完之后队头就是此时最优的 j j ,然后算出,生成直线 i i ,然后删除队尾不优的,再将直线放入队列,作为以后的 j j <script type="math/tex" id="MathJax-Element-860">j</script>。
代码如下:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
const int e = 5e4 + 5;
int n, q[e], t = 1, w = 1, tot;
long long a[e], b[e], f[e], h[e], l[e];
struct point
{
long long w, l;
}c[e];
inline int read()
{
char ch; int res;
while (ch = getchar(), ch < '0' || ch > '9');
res = ch - 48;
while (ch = getchar(), ch >= '0' && ch <= '9')
res = res * 10 + ch - 48;
return res;
}
inline bool cmp(const point &c, const point &d)
{
if (c.l == d.l) return c.w < d.w;
else return c.l < d.l;
}
inline double calc(int i, int j)
{
return f[j] + h[j + 1] * l[i];
}
inline bool slope(int p1, int p2, int p3)
{
return (b[p3] - b[p1]) * (a[p2] - a[p1])
- (b[p2] - b[p1]) * (a[p3] - a[p1]) >= 0;
}
int main()
{
int i;
n = read();
for (i = 1; i <= n; i++)
{
c[i].w = read();
c[i].l = read();
}
sort(c + 1, c + n + 1, cmp);
for (i = 1; i <= n; i++)
{
while (tot && c[i].w >= h[tot]) tot--;
h[++tot] = c[i].w; l[tot] = c[i].l;
}
a[0] = h[1];
for (i = 1; i <= tot; i++)
{
while (t < w && calc(i, q[t]) >= calc(i, q[t + 1])) t++;
f[i] = calc(i, q[t]);
a[i] = h[i + 1];
b[i] = f[i];
while(t < w && slope(q[w - 1], q[w], i)) w--;
q[++w] = i;
}
cout << f[tot] << endl;
return 0;
}