Subscribed unsubscribe Subscribe Subscribe

0w1

CFR 487 B. Strip ( DP, Monotonic Deque )

Problem - B - Codeforces

題意:
給你 n 個數組成的數列 a,和總和上限 s,長度下限 l。求將數列 a 分割成最少數量的連續片段,滿足每個片段總和不超過 s,長度不小於 l。求最少可能片段數量。

資料規模:
1 ≤ n ≤ 1e5, 0 ≤ s ≤ 1e9, 1 ≤ l ≤ 1e5,- 1e9 ≤ ai ≤ 1e9
TL: 1000 ms
ML: 256 MB

解法:
感覺就很動態規劃,為了加速轉移,需要先預處理 max_reach[ i ] : 以下標 i 當新的片段的左界時,最遠的右界下標,為了方便,是左閉右開 [ i, max_reach[ i ] )。雖然用 RMQ 比較不會錯 ( 而且當時比的人大部份都用 RMQ ),但難得可以用單調性解決:從數列的右邊掃到左邊,同時維護最小值單調雙向佇列和最大值單調雙向佇列。當掃到下標 i 時,在最大值和最小值的差大於 s 的前提下不斷從兩個雙向佇列中把比較後面的下標 pop 出來。最後一個 pop 的下標便是 max_reach[ i ],但是,如果完全不需要 pop 就是合法的,max_reach[ i ] = max_reach[ i + 1 ]。現在考慮如何動態規劃:
dp[ i ] : 已考慮前 i 個元素,此時最小可能片段數。
dp[ 0 ] = 0,
dp[ i ] = min{ dp[ t ], for all t such that t < i and max_reach[ t ] >= i and t + l >= i } + 1
因為 max_reach 成單調遞增,所以可以對 dp 維護最小值單調雙向佇列。至於 l 的限制,就只是要延遲對應的元素進隊的時間。

時間 / 空間複雜度:
O( N )

int N, S, L;
vi A;

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

vi dp;
vi max_reach;

void preprocess(){
  max_reach = vi( N + 1, N );
  deque< int > dqmin, dqmax;
  for( int i = N - 1; i >= 0; --i ){
    while( true ){
      if( dqmin.empty() or A[ dqmin.back() ] < A[ i ] ){
        dqmin.emplace_back( i );
        break;
      }
      dqmin.pop_back();
    }
    while( true ){
      if( dqmax.empty() or A[ dqmax.back() ] > A[ i ] ){
        dqmax.emplace_back( i );
        break;
      }
      dqmax.pop_back();
    }
    max_reach[ i ] = max_reach[ i + 1 ];
    while( true ){
      if( A[ dqmax.front() ] - A[ dqmin.front() ] <= S ){
        break;
      }
      max_reach[ i ] = max( dqmax.front(), dqmin.front() );
      if( dqmax.front() < dqmin.front() ){
        dqmin.pop_front();
      } else if( dqmin.front() < dqmax.front() ){
        dqmax.pop_front();
      } else{
        assert( false );
      }
    }
  }
  dp = vi( N + 1, INF );
  dp[ 0 ] = 0;
  deque< int > dq;
  for( int i = 1; i <= N; ++i ){
    if( i - L >= 0 ){
      while( true ){
        if( dq.empty() or dp[ dq.back() ] < dp[ i - L ] ){
          dq.emplace_back( i - L );
          break;
        }
        dq.pop_back();
      }
    }
    while( not dq.empty() and max_reach[ dq.front() ] < i ){
      dq.pop_front();
    }
    if( not dq.empty() ){
      upmin( dp[ i ], dp[ dq.front() ] + 1 );
    }
  }
}

void solve(){
  if( dp[ N ] == INF ){
    cout << -1 << endl;
  } else{
    cout << dp[ N ] << endl;
  }
}