【BZOJ3156】防御准备(斜率优化)

传送门


题解:

f [ i ] f[i] f[i]表示在 i i i处放置最左端的守卫塔时候,考虑所有 i i i以及 i i i右边的情况的最小花费。然后在 i i i左边全部放上木偶,则答案为 min ⁡ 1 ≤ i ≤ n ( f [ i ] + ( i − 1 ) ∗ i / 2 ) \min\limits_{1\leq i \leq n}(f[i]+(i-1)*i/2) 1inmin(f[i]+(i1)i/2)

对于 f [ i ] f[i] f[i]我们有状态转移方程:

f [ i ] = min ⁡ j = i + 1 n ( f j + ( j − i ) ( j − i + 1 ) / 2 ) + a i f[i]=\min_{j=i+1}^n(f_j+(j-i)(j-i+1)/2)+a_i f[i]=j=i+1minn(fj+(ji)(ji+1)/2)+ai

转化得到: f [ i ] = min ⁡ j > i ( f j + ( j − 1 ) j / 2 − i j ) + ( i + 1 ) i / 2 + a [ i ] f[i]=\min_{j>i}(f_j+(j-1)j/2-ij)+(i+1)i/2+a[i] f[i]=j>imin(fj+(j1)j/2ij)+(i+1)i/2+a[i]

( − j , f [ j ] + ( j − 1 ) ∗ j / 2 ) (-j,f[j]+(j-1)*j/2) (j,f[j]+(j1)j/2)为点建立下凸壳,以 i i i为斜率询问即可。


代码:

#include<bits/stdc++.h>
#define ll long long
#define re register
#define gc get_char
#define cs const

namespace IO{
	inline char get_char(){
		static cs int Rlen=1<<22|1;
		static char buf[Rlen],*p1,*p2;
		return (p1==p2)&&(p2=(p1=buf)+fread(buf,1,Rlen,stdin),p1==p2)?EOF:*p1++;
	}
	
	template<typename T>
	inline T get(){
		char c;
		while(!isdigit(c=gc()));T num=c^48;
		while(isdigit(c=gc()))num=(num+(num<<2)<<1)+(c^48);
		return num;
	}
	inline int getint(){return get<int>();}
}
using namespace IO;

using std::cerr;
using std::cout;

cs int N=1e6+6;

int n;
struct Point{
	ll x,y;
	Point(){}
	Point(cs ll &_x,cs ll &_y):x(_x),y(_y){}
	friend Point operator+(cs Point &a,cs Point &b){return Point(a.x+b.x,a.y+b.y);}
	friend Point operator-(cs Point &a,cs Point &b){return Point(a.x-b.x,a.y-b.y);}
	friend ll operator*(cs Point &a,cs Point &b){return a.x*b.y-b.x*a.y;}
};

inline ll calc(cs Point &p,cs ll &k){
	return p.x*k+p.y;
}

Point p[N];int siz,now=1;

inline void push(cs Point q){
	while(siz>=1+now&&(p[siz]-q)*(p[siz-1]-q)>=0)--siz;
	p[++siz]=q;
}

inline ll find_min(ll k){
	while(now<siz&&calc(p[now+1],k)<=calc(p[now],k))++now;
	return calc(p[now],k);
}

int a[N];
ll f[N],ans=1e18;
signed main(){
	//freopen("defend.in","r",stdin);
	n=getint();
	for(int re i=1;i<=n;++i)a[i]=getint();f[n]=a[n];
	push(Point(-n,f[n]+(ll)(n-1)*n/2));ans=f[n]+(ll)n*(n-1)/2;
	for(int re i=n-1;i;--i){
		f[i]=find_min(i)+(ll)(i+1)*i/2+a[i];
		ans=std::min(ans,f[i]+(ll)(i-1)*i/2);
		push(Point(-i,f[i]+(ll)(i-1)*i/2));
	}
	cout<<ans<<"\n";
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值