你有一支由 n 名预备役士兵组成的部队,士兵从 1 到 n 编号,要将他们拆分 成若干特别行动队调入战场。出于默契的考虑,同一支特别行动队中队员的编号 应该连续,即为形如(i, i + 1, ..., i + k)的序列。 编号为 i 的士兵的初始战斗力为 xi ,一支特别行动队的初始战斗力 x 为队内 士兵初始战斗力之和,即$ x = x_i + x_{i+1} + ... + x_{i+k}$。
通过长期的观察,你总结出一支特别行动队的初始战斗力 x 将按如下经验公 式修正为 \(x':x'= ax^2+bx+cx\) ,其中 a, b, c 是已知的系数(a < 0)。 作为部队统帅,现在你要为这支部队进行编队,使得所有特别行动队修正后 战斗力之和最大。试求出这个最大和。
例如,你有 4 名士兵,$ x_1 = 2, x_2 = 2, x_3 = 3, x_4 = 4$经验公式中的参数为 a = –1, b = 10, c = –20。此时,最佳方案是将士兵组成 3 个特别行动队:第一队包含士兵 1 和士兵 2,第二队包含士兵 3,第三队包含士兵 4。特别行动队的初始战斗力分 别为 4, 3, 4,修正后的战斗力分别为 4, 1, 4。修正后的战斗力和为 9,没有其它 方案能使修正后的战斗力和更大。
\(n \leq 10^6\)
考虑dp,容易列出dp方程
\[ dp[i]=\max\{ dp[j] +a(s[i]-s[j])^2 + b(s[i] - s[j]) +c\} (j<i) \]
\(dp[i]\)表示前\(i\)个的最大修正战斗力,\(s[i]\)是初始战斗力的前缀和。
显然是\(O(n^2)\)的,观察式子,发现可以变形做斜率优化。
令\(j>k\)(这个很重要,保证了除式大于0,至于为什么要这样,那是尝试得出来的),假设\(j\)比\(k\)优,得到
\[ dp[j] +as[j]^2 -bs[j] - (dp[k] + as[k]^2 -bs[k]) > 2as[i](s[j]-s[k]) \]
然后这里由于\(a<0\),除过去不利于分析,所以保留在右方,这时\(j > k\)的作用体现出来了,\(s[j] > s[k]\)保证除过去不变号,且横坐标递增。
\[ \frac{dp[j] +as[j]^2 -bs[j] - (dp[k] + as[k]^2 -bs[k])}{s[j]-s[k]} > 2as[i] \]
然后是\(>\)号,且横坐标\(s\)递增,所以维护上凸包。
由于\(a<0\),所以\(2as\)递减,所以可以用单调队列。
简单来说,能用单调队列的是
时间复杂度\(O(n)\)
没写return
,-Wall
又不知道怎么自动关了,调得很难受。感谢W学姐帮我看出来。
#include<iostream>#include<cstdio>#include<cstdlib>#include<cmath>#include<set>#include<map>#include<queue>#include<stack>#include<algorithm>#include<bitset>#include<cassert>#include<ctime>#include<cstring>#define rg register#define il inline#define co consttemplate<class T>il T read(){ rg T data=0; rg int w=1; rg char ch=getchar(); while(!isdigit(ch)) { if(ch=='-') w=-1; ch=getchar(); } while(isdigit(ch)) { data=data*10+ch-'0'; ch=getchar(); } return data*w;}template<class T>T read(T&x){ return x=read<T>();}using namespace std;typedef long long ll;co int N=1e6+2;ll a,b,c;ll x[N];ll dp[N];ll Up(int j,int k){ return dp[j]+a*x[j]*x[j]-b*x[j]-dp[k]-a*x[k]*x[k]+b*x[k];}ll Down(int j,int k){ return x[j]-x[k];}ll Cal(int i,int j){ return dp[j]+a*(x[i]-x[j])*(x[i]-x[j])+b*(x[i]-x[j])+c; // edit 1: return}int q[N];int main(){// freopen(".in","r",stdin);// freopen(".out","w",stdout); int n=read<int>(); read(a),read(b),read(c); for(int i=1;i<=n;++i) x[i]=x[i-1]+read<int>(); int head=0,tail=0; q[tail++]=0; for(int i=1;i<=n;++i) { while(head+1<tail&&Up(q[head+1],q[head])>=2*a*x[i]*Down(q[head+1],q[head])) ++head; dp[i]=Cal(i,q[head]); while(head+1<tail&&Up(i,q[tail-1])*Down(q[tail-1],q[tail-2])>=Up(q[tail-1],q[tail-2])*Down(i,q[tail-1])) --tail; q[tail++]=i; } printf("%lld\n",dp[n]); return 0;}