Luogu P2900 [USACO08MAR]土地征用Land Acquisition

这是一道有技巧的斜率优化题。(不会斜率优化,请戳这里)

a i , b i 分 别 为 第 i 块 土 地 的 长 、 宽 a_i,b_i分别为第i块土地的长、宽 ai,bii

首先,我们按长为第一关键字,宽为第二关键字排序,使得长为非下降序列,当长相等时,宽为非下降序列。因为如果一块土地长,宽都大,就可以把比它长宽都小的土地吃掉,所以我们排序后,再删去一些不需要保留的土地,使得所有土地的宽单调递减。

#define a(i) a[i].a
#define b(i) a[i].b
struct node
{
	ll a,b;
	bool operator <(node c)const{return a==c.a?b<c.b:a<c.a;}//这是重载运算符
}a[N];
//bool cmp(node x,node y){return x.a==y.a?x.b<y.b:x.a<y.a;}
//重载运算符不会打,那就只打上面的一行。

		scanf("%d",&n);
		for(int i=1;i<=n;i++)
			scanf("%lld%lld",&a(i),&b(i));
		sort(a+1,a+n+1);
		int t=1;
		for(int i=2;i<=n;i++)
		{
			while(t&&b(i)>=b(t))t--;
			a[++t]=a[i];
		}
		n=t;

为什么要这样处理呢? 证明:
i &lt; j &lt; k 且 a i &lt; a j &lt; a k 且 b i &gt; b j &gt; b k i&lt;j&lt;k且a_i&lt;a_j&lt;a_k且b_i&gt;b_j&gt;b_k i<j<kai<aj<akbi>bj>bk,我们选择 i , k 为 一 块 , j 为 一 块 的 话 i,k为一块,j为一块的话 i,kj,费用为 a k ∗ b i + a j ∗ b j a_k*b_i+a_j*b_j akbi+ajbj。但是, 把 i , j , k 分 成 一 块 的 费 用 只 需 a k ∗ b i 。 把i,j,k分成一块的费用只需a_k*b_i。 i,j,kakbi
所以,我们排序、删土地后,要取连续的一段。

接着,我们设 f [ i ] f[i] f[i]表示对前 i i i块土地分成若干个连续块的最小费用。
则有: f [ i ] = m i n    f [ j − 1 ] + a i ∗ b j     ( 0 &lt; j &lt; i ) f[i]=min~~{f[j-1]+a_i*b_j}~~~(0&lt;j&lt;i) f[i]=min  f[j1]+aibj   (0<j<i)
把它转化为 y = k x + b y=kx+b y=kx+b的形式:
f [ j − 1 ] = ( − a i ) ∗ b j + f [ i ] f[j-1]=(-a_i)*b_j+f[i] f[j1]=(ai)bj+f[i]
        y       =       k        x    +    b ~~~~~~~y~~~~~=~~~~~k~~~~~~x~~+~~b        y     =     k      x  +  b
认真看链接的同学知道用斜率优化的题有以下性质:

1.

斜率 ( k , 即 本 题 中 的 − a i ) (k,即本题中的-a_i) (kai)具有单调性.

2.

横坐标 ( x ) (x) x单调递增

3.

决策点(继承点)有序。
一般来说,当前处理的状态(如此题中的 f [ i ] f[i] f[i])前面符号正负最好与取最大值还是最小值有关。
对笔者而言,取最小值时前面符号最好为正,反之最好为负。

但是,上面的 b j b_j bj并不单调递增啊。
没事,我们可把上式变为: f [ j − 1 ] = a i ∗ ( − b j ) + f [ i ] f[j-1]=a_i*(-b_j)+f[i] f[j1]=ai(bj)+f[i]
这样, k , x 都 单 调 递 增 , 且 满 足 f [ i ] 前 面 是 正 号 , 就 可 以 用 斜 率 优 化 了 。 k,x都单调递增,且满足f[i]前面是正号,就可以用斜率优化了。 k,xf[i],

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define a(i) a[i].a
#define b(i) a[i].b
using namespace std;
typedef long long ll;
const int N=5e4+10;
int n,q[N],l,r;
ll f[N];
struct node
{
	ll a,b;
	bool operator <(node c)const{return a==c.a?b<c.b:a<c.a;}
}a[N];
inline ll x(int i){return -b(i+1);}
inline ll y(int i){return f[i];}
inline bool pd(int i,int j,int k)
{
	return (y(j)-y(i))*(x(k)-x(j))<(y(k)-y(j))*(x(j)-x(i));
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&a(i),&b(i));
	sort(a+1,a+n+1);
	int t=1;
	for(int i=2;i<=n;i++)
	{
		while(t&&b(i)>=b(t))t--;
		a[++t]=a[i];
	}
	n=t;
	l=r=1;q[1]=0;
	for(int i=1;i<=n;i++)
	{
		while(l<r&&y(q[l+1])-y(q[l])<=a(i)*(x(q[l+1])-x(q[l])))l++;
		int j=q[l];
		f[i]=f[j]+b(j+1)*a(i);
		while(l<r&&!pd(q[r-1],q[r],i))r--;
		q[++r]=i;
	}
	printf("%lld\n",f[n]);
	return 0;
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值