题目描述
输入描述
* 第1行: 一个数: N
* 第2..N+1行: 第i+1行包含两个数,分别为第i块土地的长和宽
输出描述
* 第一行: 最小的可行费用.
输入样例
4
100 1
15 15
20 5
1 100
输出样例
500
提示
FJ分3组买这些土地: 第一组:100x1, 第二组1x100, 第三组20x5 和 15x15 . 每组的价格分别为100,100,300, 总共500.
题目分析
BZOJ日常难题,看到FJ就知道是USACO了。
①算法确定
二话不说,看到最少,就考虑一下DP吧!
②算法结构
A.O(n^2)
首先这道题说“这一组的价值是最大的长乘最大的宽”(长和宽不是小学生的长短区别了,宽可以比长要长。设a[i].x为长,a[i].y为宽)。在一段区间中找最大,最简单的方法是先排序。我们按x的降序为第一关键字,y的降序为第二关键字,排序。
那么我们是否可以得出f[i]=f[j]+a[j+1].x*a[j+1].y呢?(假设1~j自己解决,j+1~i为一组购买)并不可以。因为虽然保证a[j+1].x是后面最大的,但是不保证a[j+1].y最大。
这种情况下,我们扫一遍整个串。如果这个土地和之前的是包含关系,那么这块土地可以不用考虑。(为什么?你还好意思问我为什么?如果这个需要考虑,那么就是把它作为最大的,但如果把它作为一组中最大的,那么还不如把它归到包含它的那一组,让它的下一个变成最大的,明显更优秀)我们用b数组存储筛掉包含关系之后的所有土地。
那么这时候就有一个特殊的性质:b数组中,x是递减的,y是递增的。
我们就能够得出关系式了:
f[i]=mymin(f[i],f[j]+b[j+1].x*b[i].y);
完整代码:
/*
O(n^2)代码
不能直接提交
*/
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int n;
struct qq{
long long x,y;
}a[51000],b[51000];
long long f[51000];
bool cmp(qq x,qq y)
{
if(x.x==y.x)return x.y>y.y;
else return x.x>y.x;
}
long long mymin(long long x,long long y)
{
return x<y?x:y;
}
int main()
{
freopen("acquire.in","r",stdin);freopen("acquire.out","w",stdout);
memset(f,127,sizeof(f));
scanf("%d",&n);
for(int i=1;i<=n ;i++)
{
scanf("%I64d %I64d",&a[i].x,&a[i].y);
}
sort(a+1,a+n+1,cmp);
int len=0;
for(int i=1;i<=n;i++)
{
if(len==0 || a[i].y>b[len].y)
{
len++;
b[len].x=a[i].x;
b[len].y=a[i].y;
}
}
//x递减,y递增
n=len;
f[1]=b[1].x*b[1].y;
for(int i=1;i<=n;i++)
{
for(int j=1;j<i;j++)
{
f[i]=mymin(f[i],f[j]+b[j+1].x*b[i].y);
}
}
printf("%I64d",f[n]);
return 0;
}
此贴终结。
B.O(nlogn)(吧)
这个代码提交上去又超时了(又?)。所以我们要用比DP还优秀的斜率优化。
首先证决策的单调性(自己证吧)
然后推导斜率方程((ノ*・ω・)ノ)
设j1<j2<i
则如果j1推出来的结果不优于j2,也就是
f[j1]+b[j1+1].x*b[i].y > f[j2]+b[j2+1].x*b[i].y
将左边只剩下f数组
f[j1]-f[j2] > b[j2+1]*b[i].y - b[j1+1].b[i].y
右边提取公因式,同时两边除以(b[j2+1].x-b[j1+1].x)
关键的一步来了:因为b数组中的x是递减的,所以j2在j1后面,x值较小,所以相减的值是负数。除后不等号方向要变
(f[j1]-f[j2])/(b[j2+1].x-b[j1+1].x) < b[i].y
得到了形如(y1-y2)/(x1-x2)的斜率式子
完整代码
成天就知道看代码
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
struct qq{
long long x,y;
}a[51000],b[51000];
long long f[51000];
int q[51000];
int n;
bool cmp(qq x,qq y)
{
if(x.x==y.x)return x.y>y.y;
else return x.x>y.x;
}
long long Y(int x)
{
return f[x];
}
long long X(int x)
{
return b[x].x;
}
double xielv(int x,int y)
{
return (Y(x)-Y(y))*1.0/(X(y+1)-X(x+1));
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld %lld",&a[i].x,&a[i].y);
}
sort(a+1,a+n+1,cmp);
int len=0;
for(int i=1;i<=n;i++)
{
if(len==0 || a[i].y>b[len].y)
{
len++;
b[len].x=a[i].x;
b[len].y=a[i].y;
}
}
int st,ed;
st=ed=1;
q[1]=0;
n=len;
for(int i=1;i<=n;i++)
{
// while(l<r && f[q[st]]-f[q[st+1]] > (b[q[st+1]+1].x-b[q[st]+1].x) * b[i].y)st++;
while(st<ed && xielv(q[st],q[st+1])< b[i].y)st++;
int j=q[st];
f[i]=f[j]+b[j+1].x*b[i].y;
while(st<ed && xielv(q[ed],i) < xielv(q[ed-1],q[ed]))ed--;
q[++ed]=i;
}
printf("%lld",f[len]);
return 0;
}