bzoj1911 [Apio2010]特别行动队
1911: [Apio2010]特别行动队
Time Limit: 4 Sec Memory Limit: 64 MBSubmit: 5224 Solved: 2571
[Submit][Status][Discuss]
Description
Input
Output
Sample Input
4
-1 10 -20
2 2 3 4
-1 10 -20
2 2 3 4
Sample Output
9
HINT
分析:dp斜率优化的裸题.
令f(i)表示分到第i个人的最大值,那么f(i) = max{f(j) + a * (sum_i - sum_j)^2 + b * (sum_i - sum_j) + c}.
把式子展开一下,可以得到:f(i) = max{-2a*sum_i*sum_j + f(j) + a * sum_j ^ 2 - b * sum_j + a * sum_i ^ 2 + b * sum_i + c}.
这其实是把关于i的部分放到了右边,与j有关的放到了左边. 右边这种和j无关的可以认为是常数(一下子就能知道.),可以提到括号外面:f(i) = max{-2a * sum_i * sum_j + f(j) + a * sum_j ^ 2 - b * sum_j} + a * sum_i^2 + b * sum_i + c.
可以发现如果j固定了,那么这个式子的取值是和sum_i相关的,可以令sum_i为自变量x,-2a * sum_j为斜率k,f(j) + a * sum_j ^ 2 - b * sum_j为截距b. 那么f(i) = kx + b + a * sum_i^2 + b * sum_i + c.
问题的关键就是如何找到一个j,使得kx + b尽量大. 维护一个双端单调队列,里面存的是直线.当枚举到新的点时,先看队首的两条直线,如果第l条在sum_i的位置的纵坐标≤第l+1条在sum_i的位置的纵坐标.那么第l条就可以丢啦!why? 随着i的增大,sum_i是不降的,那么斜率也是不降的,既然纵坐标已经≤了,并且斜率也≤,那么这条直线以后肯定不会带来贡献,丢掉!
接着取出队首元素j,j对应的k和b就用来更新f(i).
然后看队尾的两个元素r-1,r.如果直线r被直线i和直线r-1完全覆盖了,也就是不可能取到最大值,那么就丢掉.怎么判断是否被完全覆盖呢?如果令r-1和i的交点的横坐标为x,纵坐标为y,如果直线r在横坐标为x时纵坐标≤y了,那么就被完全覆盖了,丢掉即可.
最后加入i.
这个单调队列的写法和平时习惯的写法有一点点区别:l = 1,r = 0 变成了 l = 0,r = 0; l <= r变成了l < r:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const ll maxn = 1000010; ll n,a,b,c,sum[maxn],q[maxn * 2],l,r,f[maxn]; ll K(ll i) { return -2 * a * sum[i]; } ll B(ll i) { return f[i] + a * sum[i] * sum[i] - b * sum[i]; } ll Y(ll i,ll j) { return sum[j] * K(i) + B(i); } bool check(int y1,int y2,int y3) { ll temp1 = (K(y1) - K(y3)) * (B(y2) - B(y1)); ll temp2 = (K(y1) - K(y2)) * (B(y3) - B(y1)); return temp1 <= temp2; } int main() { scanf("%lld",&n); scanf("%lld%lld%lld",&a,&b,&c); for (int i = 1; i <= n; i++) { scanf("%lld",&sum[i]); sum[i] += sum[i - 1]; } l = 0,r = 0; for (int i = 1; i <= n; i++) { while (l < r && Y(q[l],i) <= Y(q[l + 1],i)) l++; f[i] = Y(q[l],i) + a * sum[i] * sum[i] + b * sum[i] + c; while (l < r && check(i,q[r],q[r - 1])) r--; q[++r] = i; } printf("%lld\n",f[n]); return 0; }