BZOJ 1597 斜率优化

题目链接: http://www.lydsy.com/JudgeOnline/problem.php?id=1597


 作为一个非权限狗,我抱着侥幸心理在BZOJ上搜了一发USACO,结果发现了这道题。。然后想出了一个DP,然而超时。。然后就百度一下,于是被安利了一个DP的经典优化技巧——斜率优化

 

完整题解如下:

  注意到有一些土地是可以被其他土地无代价地“带掉”的。设第i块土地的长宽分别为a[i]b[i],则“无用”的土地i满足a[i]<=a[j] && b[i]<=b[j]。怎么判断呢?当然是排序。以a为第一关键字、b为第二关键字降序排序。接着维护一个单调队列。如果队尾的b小于等于队首的b,则将队尾指针+1。仅是每个单调队列的队首元素才是我们需要保留的。

  删去无用的边之后,由于a是降序排好的,所以一个显然的结论是b是升序的。这样一来,合并在一起买的土地一定是队列中的连续区间。

  锵锵锵!DP方程很好写啦~ f[i]为买前i块地(队列中的顺序)的最小花费,则有

f[i]=min{ f[j]+ a[j+1]*b[i] | 0<=j<i }

      但这个是O(n^2)的,会超时。下面就用斜率优化。设在当前的状态f[i]时,从f[j]转移比f[k]优。那么

                                 f[j]+a[j+1]*b[i]<f[k]+a[k+1]*b[i]  ==>  b[i]<(f[k]-f[j])/(a[j+1]-a[k+1])
      说明,如果jk满足上述要求,k可以无视了,因为j一定比k更优。什么意思呢?再强调一次:b是不下降的序列!

         d(j,k)=(f[k]-f[j])/(a[j+1]-a[k+1]),当前的bb[now],则若i<j<kd(j,k)<d(i,k),那么j就是一个无用决策。这就满足了单调性,维护一个单调队列即可。因此,DP的时间复杂度减为O(n)。整个算法的时间复杂度为排序的复杂度,O(nlogn)。实现细节还是需要注意的,详见代码。

       关于斜率优化的理论依据,可参见2006年国家集训队汤泽的论文《从一类单调性问题看算法的优化》。简单来说,就是维护一个下凸性的函数。


// BZOJ 1597

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;

 typedef long long LL;
 typedef unsigned long long uLL;
 const int N=50000+5;

 #define rep(i,a,b) for (int i=a; i<=b; i++)
 #define dep(i,a,b) for (int i=a; i>=b; i--)
 #define read(x) scanf("%d", &x)
 #define uLL unsigned long long 

 int Q[2*N], head, tail, n, cnt;
 uLL f[N];

 struct Item {
 	int a, b;
 	bool operator < (const Item B) const{
 		return (B.a<a || (B.a==a && (B.b<b)));
 	} 
 }item[N], chosen[N];

 double calc(int j, int k) {
 	return (double)(f[k]-f[j])/(chosen[j+1].a-chosen[k+1].a);
 }

int main()
{
	read(n);
	rep(i,1,n) read(item[i].a), read(item[i].b);

	sort(item+1, item+n+1);
	cnt=0; head=tail=1;
	while (head<=tail && tail<=n) {	
		while (head<=tail && item[head].b>=item[tail].b && tail<=n) tail++;
		chosen[++cnt]=item[head];
		head=tail;
	}
 
    head=0; tail=0; f[1]=0;
	rep(i,1,cnt) {
		while (head<tail && calc(Q[head], Q[head+1])<chosen[i].b) head++; 
		int t=Q[head];
		f[i]=f[t]+(uLL)chosen[i].b*chosen[t+1].a;
		while (head<tail && calc(Q[tail], i)<calc(Q[tail-1], Q[tail])) tail--;
		Q[++tail]=i;
	}

	printf("%llu\n", f[cnt]);

	return 0;
}


posted @ 2015-12-10 20:46  Armeria  阅读(162)  评论(0编辑  收藏  举报