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])
说明,如果j和k满足上述要求,k可以无视了,因为j一定比k更优。什么意思呢?再强调一次:b是不下降的序列!
设d(j,k)=(f[k]-f[j])/(a[j+1]-a[k+1]),当前的b为b[now],则若i<j<k且d(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; }