[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:怎么说呢?怪到大样例身上去吧。

posted on 2019-07-16 18:03  autoint  阅读(113)  评论(0)    收藏  举报

导航