P3628 [APIO2010]特别行动队 单调队列优化dp
P3628 [APIO2010]特别行动队
转移方程不难写出:设 \(f_i\) 表示把前 \(i\) 个士兵分成若干组的最优值。
那么转移就是:
\[f_{i}=\max\limits_{0\le k\le i-1}\{f_k+a\times (sum_i-sum_k)^2+b\times (sum_i-sum_k)+c \}
\]
拆开后移项变成了:
\[f_k+a\times sum_k^2-b\times sum_k=2a\times sum_i\times sum_k+f_i-a\times sum_i^2-b\times sum_i-c
\]
这就是一个 \(y=kx+b\) 的形式,注意到因为 \(a<0\) 所以斜率单调递减,所以可以用单调队列优化。
注意:
- 写方程式不要错写漏写
- 注意关注数据范围
#include<bits/stdc++.h>
#define dd double
#define ld long double
#define ll long long
#define int long long
#define uint unsigned int
#define ull unsigned long long
#define N 1000010
#define M number
using namespace std;
const int INF=0x3f3f3f3f;
template<typename T> inline void read(T &x) {
x=0; int f=1;
char c=getchar();
for(;!isdigit(c);c=getchar()) if(c == '-') f=-f;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
x*=f;
}
int n,x[N],sum[N],q[N],l,r,a,b,c,f[N];
inline dd calc_k_1(int k){
return 2*a*sum[k];
}
inline dd calc_x(int k){
return sum[k];
}
inline dd calc_y(int k){
return f[k]+a*sum[k]*sum[k]-b*sum[k];
}
inline dd calc_k_2(int k1,int k2){
dd x1=calc_x(k1),x2=calc_x(k2);
dd y1=calc_y(k1),y2=calc_y(k2);
// printf("%lf %lf %lf %lf\n",x1,y1,x2,y2);
return (y1-y2)/(x1-x2);
}
signed main(){
// freopen("my.in","r",stdin);
// freopen("my.out","w",stdout);
read(n);read(a);read(b);read(c);
for(int i=1;i<=n;i++){
read(x[i]);
sum[i]=sum[i-1]+x[i];
}
q[++r]=0;
for(int i=1;i<=n;i++){
// while(top>=2&&calc_k_2(sta[top],sta[top-1])<calc_k_1(i)) top--;
while(l<r-1&&calc_k_2(q[l+1],q[l+2])>calc_k_1(i)){
// printf("l:%lld %lf %lf\n",l+1,calc_k_2(q[l+1],q[l+2]),calc_k_1(i));
l++;
}
if(l<r){
int now=q[l+1];
// printf("i:%d now:%d\n",i,now);
// printf("head:%lld\n",l+1);
f[i]=f[now]+a*(sum[i]-sum[now])*(sum[i]-sum[now])+b*(sum[i]-sum[now])+c;
}
// while(top>=2&&calc_k_2(sta[top-1],sta[top])<calc_k_2(sta[top],i)) top--;
// sta[++top]=i;
while(l<r-1&&calc_k_2(q[r-1],q[r])<=calc_k_2(q[r],i)) r--;
q[++r]=i;
}
// for(int i=1;i<=n;i++) printf("i:%d f:%d\n",i,f[i]);
printf("%lld",f[n]);
return 0;
}