[洛谷2900][USACO08MAR]土地征用Land Acquisition

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

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

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

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

因此,考虑dp。设 f[i] f [ i ] 表示前 i i 个土地的最小费用。易得转移方程:f[i]=min(f[i],f[j]+h[j+1]l[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 最大的。注意到l[i]是递增的,而 h[i] h [ i ] 是递减的。如图1:

这里写图片描述

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

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

这里写图片描述

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

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

总结一下就是,先将土地排序,去掉没有存在必要的土地,然后进行 dp d p ,先删除队头不优的,删完之后队头就是此时最优的 j j ,然后算出f[i],生成直线 i 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;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值