bzoj1911 [Apio2010]特别行动队

1911: [Apio2010]特别行动队

Time Limit: 4 Sec  Memory Limit: 64 MB
Submit: 5224  Solved: 2571
[Submit][Status][Discuss]

Description

Input

Output

Sample Input

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;
}

 

posted @ 2018-02-26 16:50  zbtrs  阅读(259)  评论(0编辑  收藏  举报