0w1

IOICJ 91 Printer ( DP, 1D / 1D Convex Optimization )

Problem Description:
You have a book with N pages in total. Page i requires cost A[ i ] to be printed. You have a weird printer, the cost of printing a continuous segment [ L, R ) is ( Sigma{ A[ i ], L ≤ i < R } ^3 + K ). You want to know the minimum total cost of printing all the pages in the book.

Constraints:
1≤T≤100
1≤N≤1e5
1≤K≤1e12
0≤ cost of any page ≤10
Sigma{ N } ≤ 1e6
TL: 1000 ms

Sample Input:
2
3 5
2 4 1
5 514
3 8 2 0 1

Sample Output:
88
2108

Solution:
pfx[ i ] : Sigma{ A[ j ], 0 ≤ j < i }
dp[ i ] : having finished printing first i pages, current minimum total cost
dp[ i ] = min{ dp[ j ] + ( pfx[ i ] - pfx[ j ] )^3 + K, 0 ≤ j < i }
It is clear that all we could do is some how get rid of the O( N ) factor in updating each dp[ i ].
Here it comes 1D / 1D convex / concave optimization. If one can prove ( with Monge Condition etc. ) that the transition is decision-monotonous, this optimization can make the complexity of the transition become O( lg N ). Note that even if the transition does not imply Monge Condition, it is still possible that the transition is decision-monotonous. Decision-monotonous implies that if the best transition source for dp[ i ] is some dp[ j ], and there is some dp[ k ], where k > i, such that the best transition source is dp[ m ], where m != j, then m > j must hold. If this property is asserted, we could accelerate the transition process by maintaining a deque that stores segments for every best decision that is known. For example, in the beginning we only know dp[ 0 ], therefore the deque initially stores information [ 0, 1, N ] which implies: currently dp[ 0 ] is the best transition source for dp[ 1, N ). Then, we traverse to dp[ 1 ], knowing that currently dp[ 0 ] is the best source, dp[ 1 ] is updated by dp[ 0 ]. Then we would look at the segment stored in the back of the deque, which is currently [ 0, 1, N ], and check whether transition source dp[ 1 ] is better than dp[ 0 ] for the first element element in that segment. If it is, do a pop_back and start looking at the last segment of the current deque again. Otherwise, binary search the first index x in that segment such that dp[ 1 ] is better than dp[ 0 ], compress that segment to x ), and push_back [ 1, x, N ]. See the code below for further comprehension.

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

const int MAXN = ( int ) 1e5;

int T;

int N;
ll K;
int A[ MAXN ];

void init(){
  cin >> N >> K;
  for( int i = 0; i < N; ++i ){
    cin >> A[ i ];
  }
}

int pfx[ MAXN + 1 ];
ll dp[ MAXN + 1 ];

ll cube( int v ){ return 1LL * v * v * v; }
ll get_cost( int x, int rb ){
  return dp[ x ] + cube( pfx[ rb ] - pfx[ x ] ) + K;
}

void preprocess(){
  for( int i = 0; i < N; ++i ){
    pfx[ i + 1 ] = pfx[ i ] + A[ i ];
  }
  deque< tuple< int, int, int > > dcs; // decision
  dcs.emplace_back( 0, 1, N ); // describes: transition from dp[ 0 ] is effective for [ 1, N ]
  for( int i = 1; i <= N; ++i ){
    while( get< 2 >( dcs.front() ) < i ) dcs.pop_front(); // right bound is out-dated
    dp[ i ] = get_cost( get< 0 >( dcs.front() ), i ); // best transition is A[ dcs.top(), i )
    while( not dcs.empty() ){
      int x, lb, rb; tie( x, lb, rb ) = dcs.back();
      if( lb <= i ) break; // will be pop_front-ed soon anyway
      if( get_cost( x, lb ) >= get_cost( i, lb ) ){
        dcs.pop_back();
        if( not dcs.empty() ){
          get< 2 >( dcs.back() ) = N;
        }
      } else{
        break;
      }
    }
    int best = -1;
    for( int lb = i + 1, rb = N, x = get< 0 >( dcs.back( ) ); lb <= rb; ){
      int mid = lb + rb >> 1;
      if( get_cost( x, mid ) > get_cost( i, mid ) ){
        best = mid;
        rb = mid - 1;
      } else{
        lb = mid + 1;
      }
    }
    if( best == -1 ) continue;
    get< 2 >( dcs.back() ) = best - 1;
    dcs.emplace_back( i, best, N );
  }
}

void solve(){
  cout << dp[ N ] << endl;
}

signed main(){
  ios::sync_with_stdio( 0 );
  cin >> T;
  for( int ti = 0; ti < T; ++ti ){
    init();
    preprocess();
    solve();
  }
  return 0;
}