• [洛谷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;
    }
    
  • 相关阅读:
    知识点:synchronized 原理分析
    知识点:spring 完全手册
    知识点:图说 Mysql 权限管理
    知识点:Mysql 基本用法之流程控制
    知识点:Mysql 基本用法之函数
    知识点:Mysql 基本用法之存储过程
    知识点:Mysql 基本用法之事务
    知识点:Mysql 基本用法之触发器
    知识点:Mysql 基本用法之视图
    知识点:MySQL表名不区分大小写的设置方法
  • 原文地址:https://www.cnblogs.com/cyf32768/p/12196311.html
Copyright © 2020-2023  润新知