您的位置:首页 > 编程语言 > C语言/C++

【OI做题记录】【BZOJ】【Usaco2008 Mar】土地购买

2016-12-02 12:47 351 查看
题目描述

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

输入描述

* 第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
);
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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息