[洛谷2900][USACO08MAR]土地征用Land Acquisition(斜率优化dp)

Solution

每块土地的长和宽分别用\(l\)\(h\)数组表示。

因为一组土地购买的价格 \(=\) 最大的长 \(*\) 最大的宽,所以对于一块土地\(x\),如果存在一块土地\(y\),满足\(l[y]>=l[x]\)\(h[y]>=h[x]\),那么它只要把土地\(x\)和土地\(y\)合为一组,最大的长可以不取\(l[x]\),最大的宽可以不取\(h[x]\)所以土地\(x\)的存在与否对答案是没有任何影响的。

因此,可以把所有土地按长度从小到大排序,对于长度相同的按宽度从小到大排序。然后维护一个栈,将排序后的土地一个一个加入,每次加入之前,先把当前栈中宽小于等于它的土地全部删除(因为这些土地长肯定小于等于它),再加入它。最后留在栈里的土地就是有存在必要的土地。

可以发现,此时留下的土地的宽度是从大到小排好的。所有还有一个结论:此时在最优决策下,每一组土地都是连续的一段。为什么呢?举个例子:假设这时有三个土地\(x\)\(y\)\(z\),是连续的三块土地,那么有:\(l[x]<l[y]<l[z]\)\(h[x]>h[y]>h[z]\)。考虑把\(x\)\(z\)分为一组,\(y\)单独一组,要花费\(h[x]*l[z]+h[y]*l[y]\)的费用。如果把\(y\)也分进\(x\)\(z\)那一组呢?只要\(h[x]*l[z]\)的费用,省去了\(y\)单独一组的费用。

因此,考虑\(dp\)。设\(f[i]\)表示前\(i\)个土地的最小费用。易得转移方程:\(f[i]=min(f[i],f[j]+h[j+1]*l[i])\),然而这是\(O(n^2)\)的,过不去。

考虑优化。求出\(f[j]\)之后,设\(a=h[j+1]\)\(b=f[j]\),就可以表示一条直线:\(y=ax+b\),求\(f[i]\)等价于在已经表示出的所有直线中,找一个当\(x=l[i]\)\(y\)最大的。注意到\(l[i]\)是递增的,而\(h[i]\)是递减的。如图1:

这里写图片描述

可以发现,一旦\(l[i]>p\)的横坐标,较优的永远是直线\(p2\)\(p1\)已经没有用了。因此维护一个单调队列,对于每一个\(i\),比较单调队列队头两个\(j\)生成的直线,只要当\(x=l[i]\)时,队头第一个不如队头第二个优,就把队头第一个删除了,直到队头第一个比第二个优。

然而还有这样一种情况,如图2:

这里写图片描述

按照之前的那种做法,过点\(A\)之前,都会选择直线\(p1\)最优,但是在过\(B\)点之后,\(p1\)已经不如\(p3\)优了。所以每次把直线\(i\)加入队列作为以后备选的\(j\)之前,先判断队尾两条直线和直线\(i\)是否有如上图的关系,有的话删除队尾第一条,直到不存在上图情况,加入直线\(i\)

判断即:\(p1\)\(p2\)交点横坐标是否大于\(p1\)\(p3\)交点横坐标。需要用到除法,可以通过移项时它变为乘法,避免因交点横坐标不为整数带来的精度问题。

总结一下就是,先将土地排序,去掉没有存在必要的土地,然后进行\(dp\),先删除队头不优的,删完之后队头就是此时最优的\(j\),然后算出\(f[i]\),生成直线\(i\),然后删除队尾不优的,再将直线\(i\)放入队列,作为以后的\(j\)

Code

#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;
}
posted @ 2020-01-15 13:57  花淇淋  阅读(112)  评论(0编辑  收藏  举报