公式

E - 石飛びの小道 / Stepping Stones Path 解説 by physics0523


踏む石を選択するのではなく、飛ばす石を選択すると考えると見通しが良くなります。これを念頭に問題を読み替えると、以下の形になります。

  • 高々 \(K\) 個の石を飛ばす。
  • ただし、連続する石を両方飛ばしてはいけない。
  • 飛ばす石のスコアの総和を最小化せよ。

この問題は以下の問題と同等です。

解法だけ抜き出すと、次の貪欲法が成立します。

  • \(A\) 中のある要素 \(A_i\) を選択したとする。
  • このとき、 \(A_{i-1},A_{i},A_{i+1}\) を削除し、ここに \(A_{i-1}+A_{i+1}-A_i\) を挿入する。
  • 上記のルールを守りながら \(A\) のうち最も小さいものを貪欲に選択することを繰り返してよい。

priority_queue と連結リストを適切に利用することで実装でき、全体の時間計算量は \(O(N \log N)\) です。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

using ll=long long;
using pl=pair<ll,ll>;

int main(){
  ll N,K;
  cin >> N >> K;
  K=min(K,(N-1)/2);
  vector<ll> A(N+2);
  ll tot=0;
  for(ll i=1;i<=N;i++){
    cin >> A[i];
    tot+=A[i];
  }
  ll cres=0;
  A[0]=1e18; A[1]=1e18;
  A[N]=1e18; A[N+1]=1e18;
  priority_queue<pl,vector<pl>,greater<pl>> pq;
  vector<ll> lef(N+2),rig(N+2);
  vector<ll> alive(N+2,1);
  for(ll i=0;i<N+2;i++){
    lef[i]=i-1;
    rig[i]=i+1;
    pq.push({A[i],i});
  }
  ll res=cres;
  ll hand=0;
  while(hand<K){
    auto od=pq.top(); pq.pop();
    if(alive[od.second]==0){continue;}
    cres+=od.first;
    res=min(res,cres);
    hand++;
    ll el=lef[od.second];
    ll er=rig[od.second];
    A[od.second]=A[el]+A[er]-A[od.second];
    pq.push({A[od.second],od.second});
    {
      alive[el]=0;
      ll x=lef[el],y=rig[el];
      lef[y]=x; rig[x]=y;
    }
    {
      alive[er]=0;
      ll x=lef[er],y=rig[er];
      lef[y]=x; rig[x]=y;
    }
  }
  cout << tot-res << "\n";
  return 0;
}

投稿日時:
最終更新: