斜率优化

BZOJ . 1010

题意:

        这道题的意思就是说有n个数,分成连续的若干段,每段(假设从第j个到第i个组成一段)的分数为 (X-L)^2,X为j-i+Sigma(Ck) ,i<=k<=j,其中L是一个常量。目标:各段分数的总和最小。

题解:

       首先这道题是一道dp题,我们设dp[ i ]表示从0 到 i 这一段的最小花费,再用一个sum[ i ]来记录一个前缀和,所以我们可以写出状态转移方程:

                        dp[ i ] = min( dp[ j ] + ( s[ i ] - s[ j ] + i - ( j  + 1 ) - L ) ^ 2;

       但是我们发现这样的话,我们要枚举i 从 1 到 n,枚举j 从 1 到 i,时间复杂度为O ( n ^2 ),而n的范围是50000,所以这样是肯定过不了的,所以程序需要优化。

       我们假设存在这样的k,使得0<j<k<=i,利用四边形不等式有关知识,我们可以发现这道问题具有决策单调性,所以若存在一个k满足比j的方案更优,则j不会用来更新dp[ i ],所以我们可以得到:

dp[ j ] + ( s[ i ] + s[ j ] + i - ( j + 1 ) - L ) ^ 2 >= dp[ k ] + ( s[ i ] + s[ k ] + i - ( k + 1 ) - L ) ^ 2

令s[ i ] + i = sum[ i ], L + 1 = c;

       经过整理我们得到:

dp[ k ] + ( sum[ k ] + c ) ^ 2 - (dp[ j ] + ( sum[ j ] + c ) ^ 2 ) <=2 * s[ i ] * ( s[ k ] - s[ j ] ) 

       我们将 ( s[ k ] - s[ j ] )  * 2移到左边就可以得到一个类似于数学中斜率求法的东西,然后我们在通过这个不等式在一个队列中来维护一个下凸壳。

       队列中存放有可能更新i的点,我们每次进队前先找到用哪一个点来更新当前的i,具体做法是判断s[ i ]和队头元素和队头后一个元素的斜率( 就是之前得到的不等式将( s[ k ] - s[ j ] )  * 2移到左边得到的式子 ),若s[ i ]小于当前斜率,那么用当前队头元素更新dp[ i ],否则头指针指向下一元素,用更新之后的头指针来更新dp[ i ],更新完dp[ i ]后,再来维护队尾,通过下凸壳来维护,这样优化之后就可以只需要跑一边每个点,就可以得到答案,时间复杂度降为O( n ),最后答案就是输出dp[ n ]。

代码:

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int N = 500001;
long long q[N],dp[N],s[N],n,l,c;

long long sq(long long a){
  return a * a;
}

long long omg(int a, int b){
  return dp[b] + sq(s[b] + c) - dp[a] - sq(s[a] + c);//double可能由精度限制,所以能用整型就用整型
}

int main(){
  scanf("%lld%lld",&n,&l);
  for (int i = 1; i <= n; i++){
      scanf("%lld",&s[i]);
      s[i] += s[i - 1];
  }
  for (int i = 1; i <= n; i++) s[i] += i;
  c = l + 1;
  int head = 1, tail = 1;
  q[tail] = 0;
  for (int i = 1; i <= n; i++){
      while (head != tail && omg(q[head],q[head + 1]) <= 2 * s[i] * (s[q[head + 1]] - s[q[head]])) head++;//先找到用哪一个元素更新当前dp[ i ];int t = q[head];
      dp[i] = dp[t] + sq(s[i] - s[t] - c);//更新dp[ i ];
      while (head != tail && omg(q[tail],i) * 2 * (s[q[tail]] - s[q[tail - 1]]) < omg(q[tail - 1],q[tail]) * 2 * (s[i] - s[q[tail]])) tail--;//维护队尾
      q[++tail] = i;//易错 : 记得把当前元素进队
  }
  printf("%lld
",dp[n]);
  return 0;
}

 

原文地址:https://www.cnblogs.com/ganster/p/8487849.html