题目
简要题意:给定一个长为(n)的序列({s_i})与常数(a,b,c),序列的一个连续子段(s_i)到(s_j)的贡献为(at^2+bt+c),(t)为子段内元素和,求一种切割序列的方法,使得总贡献最大,输出最大值
Solution
前置技能:Dp、斜率优化
这题看上去和(APIO2010)序列分割比较像,只是那道题更简单,要能做出这题首先在看到时就至少要想到(O(n^2))的Dp,如果这个不会的话建议不要做这题,Dp方程:
(f[i]=max{f[j]+v(sum_{k=i}^js[k])},v(x))为计算贡献的函数
考虑到题目给的数据明显是(O(n))的算法,也就是斜率优化的队列可以解决,首先设序列前缀和为(s)(之后不会提及原序列,用(s)表示序列前缀和)
(f[i]=max{f[j]+v(s[j]-s[i])},jin [1,i))
考虑从(k)转移比(j)优,则有
(f_j+a(s_i-s_j)^2+b(s_i-s_j)+cleq f_k+a(s_i-s_k)^2+b(s_i-s_k)+c)
消去同类项
(f_j+as_j^2-2as_is_j-bs_jleq f_k+as_k^2-2as_is_k-bs_k)
将(a,b)分别汇总
(f_j+b(s_k-s_j)leq f_k+a(2s_is_j-2s_is_k+s_k^2-s_j^2))
提出右边的公因式
(f_j+b(s_k-s_j)leq f_k+a(s_j-s_k)(2s_i-s_j-s_k))
左右拥有公因式合并
(f_j-f_kleq (s_j-s_k)igl[a(2s_i-s_j-s_k)+bigr])
转移(分式居中看得清楚一些)
[frac {f_j-f_k}{s_j-s_k}leq a(2s_i-s_j-s_k)+b
]
剔除含(i)的部分(这里别忘记(a<0),不等式要反向)
[2s_igeq frac {frac {f_j-f_k}{s_j-s_k}-b}a+s_j+s_k
]
单调队列维护即可
Code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rg register
template <typename _Tp> inline _Tp read(_Tp&x){
char c11=getchar(),ob=0;x=0;
while(c11^'-'&&!isdigit(c11))c11=getchar();if(c11=='-')ob=1,c11=getchar();
while(isdigit(c11))x=x*10+c11-'0',c11=getchar();if(ob)x=-x;return x;
}
const int N=1001000;
int q[N],he,ta,n;
ll f[N],s[N],a,b,c;
inline double slp(int i,int j)
{return (((1.0*f[j]-f[i])/(1.0*s[j]-s[i]))-b)/a+(1.0*s[i]+s[j]);}
int main(){
read(n);read(a);read(b);read(c);he=ta=1;
for(rg int i=1;i<=n;++i)read(s[i]),s[i]+=s[i-1];
for(rg int i=1,j;i<=n;++i){
while(he<ta&&slp(q[he],q[he+1])<=s[i]+s[i])++he;j=q[he];
f[i]=f[j]+a*(s[i]-s[j])*(s[i]-s[j])+b*(s[i]-s[j])+c;
while(he<ta&&slp(q[ta],i)<=slp(q[ta-1],q[ta]))--ta;
q[++ta]=i;
}printf("%lld
",f[n]);
return 0;
}