bzoj1911&CodeVS1318 特别行动队

http://www.lydsy.com/JudgeOnline/problem.php?id=1911 http://codevs.cn/problem/1318/

题意:给出n个数,要求把它们分成若干个连续段。对于一个数之和为s的连续段,得分为f(s)=a*s*s+b*s+c,其中a,b,c已经给定且a是负数。求最大的总得分。要求线性复杂度。

很容易想到这道题的一个DP算法:设dp[i]为前i个数分成若干个连续段能获得的最大的总得分,且dp[0]=0。则有dp[i]=max{dp[j]+f(sigma{w[k]}(j<k<=i))}(0<=j<i)。

但是,这样的算法是二次方的,不能满足本题的要求。应该注意到本题中分数的计算方式的特殊性,来寻找突破口。

令s[i]=w[1]+...+w[i],则dp[i]=max{dp[j]+f(s[i]-s[j])}(0<=j<i)=max{dp[j]+a*s[i]*s[i]+a*s[j]*s[j]-2*a*s[i]-s[j]+b*s[i]-b*s[j]+c}(0<=j<=i)

=a*s[i]*s[i]+b*s[i]+c+max{dp[j]+a*s[j]*s[j]-2*a*s[i]-s[j]-b*s[j]}(0<=j<i}。这一步要注意分离出已知量,然后把与i有关和与j有关的量分开。

如果令k=2*a*s[i]+b,x=s[j],y=dp[j]+a*s[j]*s[j],G为max{}的内容,则有y=k*x+G。看到这个式子,如果熟悉斜率优化DP,就很容易做出这个题目了。

但是,很多人并不了解斜率优化DP,于是我复制了一段话,并稍作修改:

得到这个式子之后,我们可以看到,k是一个常数(由当前枚举的i在O(1)时间内计算得出)。将这个式子看成是一个直线的函数表达式的话,k就是斜率,也就是说这是一个斜率固定的直线。
y和x则是和j有关的常量。而j的这些值应该都已经在之前计算过了。(因为j<i)
这个式子中,G是未知的,G和y以及x有关。
如果我们把每个j对应的x和y值看成一个坐标系中的点的话。
那么当我们枚举到i时,坐标系中就有一系列的点。
对于每个点,做一条斜率为k的直线,就能得到一个G的值。G的值为这条直线与y轴交点的纵坐标。
我们可以看到,实际上,如果我们让G的值由负无穷变化到正无穷,相当于一条直线,它满足斜率为k,然后从坐标系的下方慢慢地向上平移到坐标系的上方。
那么,我们要找到,G的最小值,就是在这个过程中,这条直线所碰到的第一个点!
而这个点,必然是这个点集的凸包上的点。(不知道凸包概念的去baidu一下好了。

再加上很关键的一点。随着i的增加,点的坐标是单调不下降的(s[i]),直线的斜率也是单调不降的(2*s[i]+b)!
满足这两个单调性,我们就可以利用单调队列,来维护一个凸壳。因为我们要找G的最小值,所以要维护一个下凸壳。方法和之前那篇数形结合题一样,类似Garham求凸包的算法。
之所以可以用单调队列是因为坐标和斜率都满足单调性的话,可以证明每个点如果不是某个i的最优决策,也不能会是之后的i的最优决策,可以被抛弃。
因为每个i只会进出队列一次,所以时间复杂度降为O(N),只需要保存单列就可以,空间复杂度为O(N),比较完美地解决了这个问题。 

代码中有一些话和以上内容稍有出入:

因为各数段的s之和必然是n个数的和,所以b*s这一项可以直接移到最外部。

维护下凸壳时,基于各横纵坐标单调上升的特殊性,直接判断斜率的大小来删点。

并不是真正的队列实现,而是模拟两个指针i和j,随着i的增加j在原基础上增加。

代码:

#include<cstdio>
#define rpt(x) for(i=1;i<=x;i++)
#define maxn 1000005
struct point{
	long double x,y;
};
long double slope(point a,point b){
	return (a.y-b.y)/(a.x-b.x);
}
point p[maxn],t;
long long a,c,s[maxn],f[maxn],tmp;
int e[maxn],g[maxn],n,i,j,k,m,A,B,C;
long long ans;
int main(){
	scanf("%d",&n);
	scanf("%d%d%d",&A,&B,&C);
	a=-A;
	c=-C;
	s[0]=0;
	rpt(n){
		scanf("%d",&e[i]);
		s[i]=s[i-1]+e[i];
	}
	k=1;
	m=0;
	rpt(n){
		f[i]=a*s[i]*s[i]+c;
		while((k<m)&&(slope(p[k],p[k+1])<2*a*s[i])) k++;
		j=g[k];
		tmp=f[j]+a*(s[i]-s[j])*(s[i]-s[j])+c;
		if(tmp<f[i]) f[i]=tmp;
		t.x=s[i];
		t.y=f[i]+a*s[i]*s[i];
		while((m>1)&&(slope(p[m],t)<slope(p[m-1],p[m]))) m--;
		p[++m]=t;
		g[m]=i;
	}
	ans=-f[n]+B*s[n];
	printf("%lld\n",ans);
	fclose(stdin);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值