斜率优化入门带简单的总结 Print Article HDU3507

总结在最下面!!!

题目链接:HDU-3507

主要思路:

题目好像忘了说C[i]\geq 0

首先要会n^{2}的DP。状态转移方程是dp[i]=min(dp[j]+cost(j+1,i)+M);

其中cost(j+1,i)=(sum[i]-sum[j])^2。将转移方程展开再整理得出(其中sum[i]^2+M是定值故拿出来)dp[i]=min(dp[j]+sum[j]^2-2*sum[i]*sum[j])+sum[i]^{2}+M;

y=dp[j]+sum[j]^2

k=2*sum[i]

x=sum[j]

d=sum[i]^2+M

dp[i]=min(y-kx)+d。

所以就在求y-kx的最小值,可以看出来这和函数解析式很像。故我们设y=kx+b,所求值就是b,也就是这个函数和y轴相交的y坐标。然后开始分析。

不难看出y,k,x这三个变量的值是单调递增的(sum数组单调递增不说了,dp数组的话,如果你多一个数花费反而变少,这明显不可能。)这里可以用一个单调队列来维护这些点的信息,为什么要用单调队列在下面解释。

性质1:

  (X比Y早进来)假设此时单调队列有X,Y两个点,此时的k为0.75。XY的两点斜率大于当前的k,那么经过X的斜率为k的直线AB与经过点Y的斜率为k的之间HG与y轴的交点的y坐标值中AB与y轴交点的y坐标较小。故此时是点X更优的。

 

此时单调队列有X,Y两个点,此时的k为3(这样明显一点)。如图,若XY两点的斜率小于当前的k,那么经过X的斜率为k的直线AB与经过点Y的斜率为k的之间EF与y轴的交点的y坐标值中EF与y轴交点的y坐标较小(当然实际中不会小于0,这里只是举个例子),故点Y比点X更优,又我们知道斜率k是递增的,故XY两点的斜率再也不会大于k,故X在之后都不会对答案有贡献,将他永久的弹出队列。(若两直线斜率相同,也可弹出X,因为k的递增性,在之后Y肯定都比X更优)

性质2:

队列内的点一定是两两之间的斜率递增的。只用证明不可能出现斜率变小即可。

证明若直线XY的斜率小于直线YZ的斜率时不可能存在某一个k使Y比X和Z更优。

记直线XY斜率为Kx,直线YZ斜率为Kz。

通过性质1,可以得出,要使Y比X更优,k>Kx。同理,要使Y比Z更优,k<Kz。

故Kx<k<Kz但是Kx>Kz,故k无解。

注意初始化一个(x=0,y=0)的点加入队列作初始值。

有了上述性质,就有了以下的算法流程:

将这些点(x,y值有当层计算得出)加入队尾,每次用性质1向后推队首,用性质2弹队尾即可。

AC代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
#define M 500005
using namespace std;
int A[M];
long long sum[M];
long long dp[M];
struct node {
	long long x,y;
} Q[M];
bool checkL(node A,node B,long long k){
	return B.y-A.y<=1ll*k*(B.x-A.x);//判断A,B两点的斜率是否小于k 
}
bool checkR(node A,node B,node C){//判断BC的斜率是否大于AB的斜率 
	return 1ll*(B.y-A.y)*(C.x-B.x)>=1ll*(C.y-B.y)*(B.x-A.x);//由于除法担心有精度问题就改成了乘法 
}
int main() {
	int n,m;
	while(~scanf("%d%d",&n,&m)) {
		for(int i=1; i<=n; i++)scanf("%d",&A[i]),sum[i]=sum[i-1]+A[i];
		memset(dp,123,sizeof(dp));
		dp[0]=0;
		int L=0,R=-1;
		Q[++R]=(node){0,0};//初始值 
		for(int i=1; i<=n; i++) {
			long long k=2*sum[i];
			while(L<R&&checkL(Q[L],Q[L+1],k))L++;//这样保证队列内有至少一个元素,故是L<R而不是L<=R 
			dp[i]=Q[L].y-1ll*k*Q[L].x+1ll*sum[i]*sum[i]+m;//计算当前的DP值 
			long long x=sum[i],y=dp[i]+1ll*sum[i]*sum[i];
			node now=(node) {x,y};
			while(L<R&&checkR(Q[R-1],Q[R],now))R--;
			Q[++R]=now;
		}
		printf("%lld\n",dp[n]);
	}
	return 0;
}

总结:

总之就是对于一个题目写出答案的表达式,把与前置状态无关的状态看做常数后变成一个函数表达式在求解即可。

对于一些K不单调的问题可以用单调栈按原样然后再二分查答案即可。

举个栗子

node Get(int K){
	int L=2,R=top,res=1;
	while(L<=R){
		int mid=(L+R)>>1;
		if(cmp(stk[mid-1],stk[mid],K)){//如果后一个比前一个更优(这里的判断和题目有关)
			res=mid;
			L=mid+1;
		}else R=mid-1;
	}
	return stk[res];
}

斜率优化这个东西建议多刷几道题,熟门熟路后再去刷其他的,不然感觉很容易忘。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值