题解-APIO2010 特别行动队
题目
简要题意:给定一个长为\(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])\},j\in [1,i)\)
考虑从\(k\)转移比\(j\)优,则有
\(f_j+a(s_i-s_j)^2+b(s_i-s_j)+c\leq f_k+a(s_i-s_k)^2+b(s_i-s_k)+c\)
消去同类项
\(f_j+as_j^2-2as_is_j-bs_j\leq 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_k\leq (s_j-s_k)\bigl[a(2s_i-s_j-s_k)+b\bigr]\)
转移(分式居中看得清楚一些)
剔除含\(i\)的部分(这里别忘记\(a<0\),不等式要反向)
单调队列维护即可
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\n",f[n]);
return 0;
}