洛谷 P2900 [USACO08MAR]土地征用Land Acquisition

题面与大意

题面很难看
大意:有一堆矩形的土地,你可以分组购买它们,每一组购买的价格是这些矩形中最大的长乘上最大的宽,问你最佳购买方案



题外话:FJ是个什么东西?


O ( n 2 ) O(n^2) O(n2)做法

这道题的普通打法是最难推的一部分

首先我们可以想到:像下面的这种情况

完全可以买一送一啊!
那我们就按长度从小到大对它们进行排序,然后将排在前面而宽度又比较小的删掉。
然后我们会发现高度是单调递增、宽度单调递减的
f i f_i fi表示买前 i i i片土地的最小花费
易得方程:
f i = f j + l i w j + 1 f_i=f_j+l_iw_{j+1} fi=fj+liwj+1
可以得60分诶!!
这数据也忒水了


优化

一开始删除土地捆绑销售时我用了 O ( n 2 ) O(n^2) O(n2)的打法
但其实在这里我们可以用单调栈的思想实现 O ( n ) O(n) O(n)排查。

然后套斜率:
f i = f j + l i w j + 1 f_i=f_j+l_iw_{j+1} fi=fj+liwj+1
f i − l i w j + 1 = f j f_i-l_iw_{j+1}=f_j filiwj+1=fj
f j = − l i w j + 1 + f i f_j=-l_iw_{j+1}+f_i fj=liwj+1+fi
其中
y = f j y=f_j y=fj
k = l i k=l_i k=li
x = − w j + 1 x=-w_{j+1} x=wj+1
b = f i b=f_i b=fi

(注意要保持 k k k x x x的单调增)
数!形!结!合!大!法!好!

上代码:
tips:要注意范围什么的,主要是 j j j后面要不要 + 1 +1 +1的问题

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
struct deque//Especially for monotone queue
{
	int head,tail;//head~tail
	/*/
	int list[110];//convinient for debug
	deque():head(1),tail(0){}
	/*/
	int *list;//avoid the stack-crashing
	deque():head(1),tail(0),list(new int[51000]){}
	//*/

	int size(){return tail-head+1;}
	
	int front(){return list[head];}
	void push_front(int x){list[--head]=x;}
	void pop_front(){++head;}
	int front_2nd(){return list[head+1];}
	
	int back(){return list[tail];}
	void push_back(int x){list[++tail]=x;}
	void pop_back(){--tail;}
	int back_2nd(){return list[tail-1];}
};//手打双端队列吼啊!
struct field
{
	int l,w;//length & width
	bool operator < (field b)const
	{return l<b.l||l==b.l&&w<b.w;}
}a[51000],b[51000];
int top;
ll f[51000];
#define Y(j) f[j]
#define k(i) b[i].l
#define X(j) -b[j+1].w
double slope(int c,int d)
{return (Y(c)-Y(d))/(double)(X(c)-X(d));}
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&a[i].l,&a[i].w);
	sort(a+1,a+n+1);
	b[top=1]=a[1];
	for(int i=2;i<=n;i++)
	{
		while(top&&b[top].w<=a[i].w)--top;//还有不要让top变成负数
		b[++top]=a[i];
	}	
	memset(f,1,sizeof(f));
	f[0]=0;
	deque q;
	q.push_back(0);
	for(int i=1;i<=top;i++)
	{
		while(q.size()>1&&slope(q.front(),q.front_2nd())<k(i))q.pop_front();
		f[i]=f[q.front()]+1ll*b[i].l*b[q.front()+1].w;
		//1ll是指在long long类型下的1,用于将计算得到值强行转换为long long
while(q.size()>1&&slope(q.back_2nd(),q.back())>slope(q.back(),i))q.pop_back();
		q.push_back(i);
	}
	printf("%lld",f[top]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值