题目链接: 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;
}