LG3628 [APIO2010]特别行动队

时间:2023-03-09 20:25:53
LG3628 [APIO2010]特别行动队

题意

你有一支由 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\)递减,所以可以用单调队列。

简单来说,能用单调队列的是

  1. 大于单调减
  2. 小于单调增

时间复杂度\(O(n)\)


没写`return`,`-Wall`又不知道怎么自动关了,调得很难受。感谢W学姐帮我看出来。
```cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define rg register
#define il inline
#define co const
templateil 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;
}
templateT read(T&x)
{
return x=read();
}
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]+ax[j]x[j]-bx[j]-dp[k]-ax[k]x[k]+bx[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();

read(a),read(b),read(c);

for(int i=1;i<=n;++i)

x[i]=x[i-1]+read();

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])>=2ax[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;

}


20190716:怎么说呢?怪到大样例身上去吧。