【题解】洛谷P3628 [APIO2010]特别行动队 斜率DP

给定n(1e6),a(-5…-1),b(±1e7),c(±1e7),一个长为n的数列(100),现在要将其分成连续的若干段。
每段的价值是 a x 2 + b x + c ax^2+bx+c ax2+bx+c,其中 x x x是这段数字的和。求最大的总价值。


d i d_i di表示从 1 1 1 i i i分好的最大价值, d 0 = 0 d_0=0 d0=0
d i = m a x { d j + a ( s i − s j ) 2 + b ( s i − s j ) + c } , 0 < = j < i d_i=max\{d_j+a(s_i-s_j)^2+b(s_i-s_j)+c\},0<=j<i di=max{dj+a(sisj)2+b(sisj)+c}0<=j<i,其中 s i s_i si表示数列的前缀和。
展开得, d i = a s i 2 + b s i + c + m a x { d j + a s j 2 − b s j − 2 a s i s j } d_i=as_i^2+bs_i+c+max\{d_j+as_j^2-bs_j-2as_is_j\} di=asi2+bsi+c+max{dj+asj2bsj2asisj}

j < k < i j<k<i j<k<i,那么“从 k k k转移优于从 j j j转移”等价于
d k + a s k 2 − b s k − 2 a s i s k > d j + a s j 2 − b s j − 2 a s i s j d_k+as_k^2-bs_k-2as_is_k>d_j+as_j^2-bs_j-2as_is_j dk+ask2bsk2asisk>dj+asj2bsj2asisj
y i = d i + a s i 2 − b s i , x i = 2 a s i y_i=d_i+as_i^2-bs_i,x_i=2as_i yi=di+asi2bsixi=2asi,因为 a < 0 a<0 a<0,可以整理得:
y k − y j x k − x j < s i \frac{y_k-y_j}{x_k-x_j}<s_i xkxjykyj<si

由斜率优化原理(参见【笔记】斜率DP),斜率小于 s i s_i si k k k j j j优对应于需要维护一个下凸包,而且 s i s_i si单调递增,可以使用双端队列维护下凸包。


我WA的好惨。。。
主要原因是 x k − x j = 2 a ( s k − s i ) x_k-x_j=2a(s_k-s_i) xkxj=2a(sksi)是一个负数,好多分析过程就不成立了。

考虑不把 a a a算在 x i x_i xi里,即 x i = 2 s i x_i=2s_i xi=2si,那么斜率式就是:
y k − y j x k − x j > a ∗ s i \frac{y_k-y_j}{x_k-x_j}>a*s_i xkxjykyj>asi
此时不管目标斜率是多少,我都应当维护一个上凸包,即相邻点的斜率单调递减。

然后因为目标斜率也是单调递减的,也可以用双端队列来维护。


总结:

  1. 尽量把分母之差 x k − x j x_k-x_j xkxj设为大于0的数,不然会很难做。
  2. 下凸包( k j k < s i k_{jk}<s_i kjk<si,相邻斜率递增)对应目标斜率 s i s_i si递增,上凸包( k j k > s i k_{jk}>s_i kjk>si,相邻斜率递减)对应目标斜率 s i s_i si递减,满足这两个条件之一时就可以使用双端队列维护凸包。
/* LittleFall : Hello! */
#include <bits/stdc++.h>
using namespace std; using ll = long long; inline int read();
const int M = 1000016, MOD = 1000000007;

ll n, A, B, C;
ll sum[M], dp[M];
int q[M], l, r;

inline ll suby(int j, int k) //计算y[k]-y[j]
{
	ll yj = dp[j] + A*sum[j]*sum[j] - B*sum[j];
	ll yk = dp[k] + A*sum[k]*sum[k] - B*sum[k];
	return yk-yj;
}
inline ll subx(int j, int k) //计算x[k]-x[j]
{
	return 2*(sum[k]-sum[j]);
}
inline ll cal_dp(int i, int j) //计算从j转移到i的dp值
{
	return dp[j] + A*(sum[i]-sum[j])*(sum[i]-sum[j]) + B*(sum[i]-sum[j]) + C;
}
int main(void)
{
	#ifdef _LITTLEFALL_
	freopen("in.txt","r",stdin);
    #endif

	n = read(), A = read(), B = read(), C = read();
	for(int i=1; i<=n; ++i)
		sum[i] = sum[i-1] + read();

	l = r = 0; q[r++] = 0;
	for(int i=1; i<=n; ++i)
	{
		while(r-l>=2 && suby(q[l],q[l+1])>=sum[i]*A*subx(q[l],q[l+1])) ++l;
		dp[i] = cal_dp(i, q[l]);
		while(r-l>=2 && 
			suby(q[r-2],q[r-1])*subx(q[r-1],i) <= subx(q[r-2],q[r-1])*suby(q[r-1],i)
		) --r;
		q[r++] = i;
	}
	cout << dp[n] << endl;

    return 0;
}


inline int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值