决策单调性Ⅱ:斜率优化(1597: [Usaco2008 Mar]土地购买)

决策单调性Ⅰ:四边形不等式:

http://blog.csdn.net/jaihk662/article/details/78174717

 

决策单调性:

  • 对于dp[i] = min(dp[i], dp[j]+w[j, i])中所有的x>y,一定满足k[x]>=k[y],其中k[x]表示x点的最优决策点

也就是说每个决策点能决策的区间一定是连续的一段,并且随着决策点的右移,这个区间也在不断右移,这样就可以用栈或者二分来O(nlogn)解决原本O(n²)的DP,而斜率优化可以处理一些特殊的单调性DP,并且能做到O(n)的复杂度

 

还是这个例子:1010: [HNOI2008]玩具装箱toy

这题的dp方程为:dp[i] = min(dp[j]+(sum[i]-sum[k]+i-j-1-L)²)(其中sum[]是前缀和,单调递增)

 

设A[i] = sum[i]+i-1-L;B[i] = sum[i]+i

有dp[i] = min(dp[j]+(A[i]-B[j])²) = A[i]²+min(-2A[i]B[j]+B[j]²+dp[j])

再设a[i] = -2A[i],x(j) = B[j],y(j) = B[j]²+dp[j]

那么方程就变成了经典形式:f(i) = min(a[i]*x(j)+y(j)),也就是G = ax+y(y = -ax+G)

而我们的目的就是找到一组(x, y)使得G最小

这就相当于空间中有若干个点(x', y'),并且有一条斜率已经确定的直线,让这条直接经过某个点,使得直线与y轴的截距最小,又因为A[i]单调递增的缘故,a = -2A[i],斜率2A[i]一定是单调递增的,同理x和y也单调递增

 

假设n=6,建模如下图:

将图中的点从左到右依次编号1,2,3,4,5,6,对应着6个决策点(别忘了还有0号点就是坐标原点)

而图中的两条绿线分别是斜率为2a[1]和2a[3]的第1,3条直线,其它直线省略未画出

DP过程如下: 

  1. 0号点入队
  2. 当遍历第一个决策点时,队列只有1个元素,直接进队
  3. 当遍历第二个决策点时,队列有2个元素(0和1),判断经过0号点和1号点的直线斜率(为了以后方便,经过x号点和y号点的直线就省略为直线xy)是否大于当前斜率2a[2],如果小于,说明0号点被1号点完爆,从次往后直线经过1号点绝对会比经过0号点更优,所以弹出0号点

假设大于,也就是当前还是0号点还是最优决策,情况如下(下图就是大于的情况)

  1. 其中粉线代表直线01,绿线代表斜率2a[2]的直线,我们不妨将绿线向下平移,直到绿线覆盖1号决策点,那么就可以发现明显还是粉线的截距更小,这就意味着粉线的G更小,还是0号点更优,所以不弹出队列
  2. 然后再比较直线01的斜率和直线12的斜率,看是否满足前者小于后者,如果不满足,说明1号点要被弹出,因为后面点要不选0号点,要不就选2号点,绝对不选1号点(维护下凸包),而上图中1号点显然不用被弹出,所以遍历完第二个点后没有任何点被弹出,并当前最优决策点还是0号点
  3. 2号点进队,此时从队尾到队首元素分别为0,1,2
  4. 遍历第三个点后,0号点被弹出(直线01斜率小于2a[3]了),1号点仍然不会被弹出,队尾搞定了,再看队首,一样维护下凸包,2号点被弹出
  5. 3号点进队,此时从队尾到队首元素分别为1,3
  6. ……

这样搞一趟下来,由于每个点只会进栈出栈1次,所以成功在O(n)时间内求出了所有的最优决策!

下面就是道模板题

 

 

1597: [Usaco2008 Mar]土地购买

Time Limit: 10 Sec  Memory Limit: 162 MB
Submit: 5286  Solved: 1961
[Submit][Status][Discuss]

Description

农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土地的长宽满足(1 <= 宽 <= 1,000,000; 1 <= 长 <= 1,000,000). 每块土地的价格是它的面积,但FJ可以同时购买多快土地. 这些土地的价格是它们最大的长乘以它们最大的宽, 但是土地的长宽不能交换. 如果FJ买一块3x5的地和一块5x3的地,则他需要付5x5=25. FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费. 他需要你帮助他找到最小的经费.

Input

* 第1行: 一个数: N

* 第2..N+1行: 第i+1行包含两个数,分别为第i块土地的长和宽

Output

* 第一行: 最小的可行费用.

Sample Input

4

100 1

15 15

20 5

1 100

Sample Output

500

 

 

首先一个显而易见的贪心,所有的土地按x[]从小到大排序,x[]相同就按y[]从小到大排序

这样下来每一组的所有土地一定是连续的

有dp方程:dp[now] = min(dp[now], dp[j]+x[now]*max(y[i], i∈(j+1, now)) )

理论上可以斜率优化时线段树维护最大值,但这样挺麻烦的

考虑再贪心:如果排序后某一块土地的x[]值小于它后面一块(其实这个是肯定的都排了序)并且y[]值也小于它后面一块,那么这一块就可以无视,因为这样的话你只用多选它后面那一块其实就ok了,这块就一定没有贡献,有贡献就一定不是最优解

这样处理完毕之后整个序列就一定满足x递增的同时y递减,dp方程变为

dp[now] = min(dp[now], dp[j]+x[now]*y[j+1])

将其转化成标准形式:y = -ax+G有a = x[now];x = y[j+1];y = dp[j]

因为斜率k和x单调递减,y单调递增,所以要维护上凸包,有方程

-x[now]<(dp[j]-dp[k])/(y[j+1]-y[k+1]),就说明k点比j点更优

 

 

#include<stdio.h>
#include<algorithm>
using namespace std;
#define LL long long
typedef struct Res
{
	LL x, y;
	bool operator < (const Res &b) const
	{
		if(x<b.x || x==b.x && y<b.y)
			return 1;
		return 0;
	}
}Res;
Res s[50005];
LL st[50005], dp[50005];
double Calc(LL j, LL k)
{
	return 1.0*(dp[j]-dp[k])/(s[j+1].y-s[k+1].y);
}
int main(void)
{
	LL n, i, cnt, L, R;
	scanf("%lld", &n);
	for(i=1;i<=n;i++)
		scanf("%lld%lld", &s[i].x, &s[i].y);
	sort(s+1, s+n+1);
	cnt = 0;
	for(i=1;i<=n;i++)
	{
		while(cnt>=1 && s[i].y>=s[cnt].y)
			cnt--;
		s[++cnt] = s[i];
	}
	n = cnt;
	L = R = 1;
	for(i=1;i<=n;i++)
	{
		while(R>L && Calc(st[L], st[L+1])>-s[i].x)
			L++;
		dp[i] = dp[st[L]]+s[i].x*s[st[L]+1].y;
		while(R>L && Calc(st[R], i)>Calc(st[R-1], st[R]))
			R--;
		st[++R] = i;
	}
	printf("%lld\n", dp[n]);
	return 0;
}

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值