公式

E - Best Performances 解説 by physics0523


この問題には様々な解法がありますが、ここでは「同じデータ構造を 22 つ使う」方針を使います。

今回はデータ構造として multiset(多重集合を管理するデータ構造) を用います。使う機能は以下の通りなので、同等の操作ができるならどれを使っても構いません。

  • 多重集合を管理する。即ち、同一の値を複数保持することができる。
  • 最大値(または最小値、またはその両方) にアクセスできる。
  • 特定の値を 11 つ削除することができる。

「大きい方から KK 個の値を管理する multiset XX 」「それ以外の値を管理する multiset YY 」「 XX の総和 ss 」という 33 つの要素を管理していくことを考えます。

最初 XX は大きい方から KK 個の値 (今回は KK 個の 00 ) 、 YYAA 内のそれ以外の値 (今回は NKN-K 個の 00 ) 、 ssXX の総和と初期化します。

以下の操作 balance, add, erase を定義します。これは 22 つの multiset で実装できます。各操作を実装していく道中で ss も更新していきます。

  • balanceXX の要素数が KK 個でないなら、 KK 個になるまで YY の要素のうち最大のものを XX に移動することを繰り返す。そのうえで、 ( XX の最小値) << ( YY の最大値) が満たされる限りこれらを入れ替えることを繰り返す。
  • addYY に特定の値 vv を追加した上で balance する。
  • erase … 特定の値 vv を削除する。 XXvv が含まれるならそれを消し、そうでないなら YY から vv を消す。その後 balance する。

(おまけ: 今回の操作では XX の要素数が KK を上回ることはないですが、 XX の要素数が KK を上回るような更新があるようなケースでも XX のうち小さい要素を YY に移動させれば上手く取り扱えます。)

AiA_i を変更する時は以下の操作をかければよいです。

  • 新しい AiA_iadd する。
  • その後、元の AiA_ierase する。

(この順番は逆でも良いですが XXYY の大きさの合計が KK を下回るケースや N=1N=1 の時に X,YX,Y が双方空になるケースなどがあり実装がしづらくなります。なお、今回の問題では番兵として 00 をたくさん入れるという対処法もあります。)

今回の問題では、 balance 内での swap の回数が O(1)O(1) であると見積もられるので、全体の計算量は O(QlogN)O(Q \log N) となります。

この解法は他の問題にも応用できますが、実装上の注意として、操作の道中で XXYY が空集合になったりする可能性があることに注意してください。

実装例 (C++):

Copy
  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. int k;
  4. multiset<int> x,y;
  5. long long s;
  6. void balance(){
  7. while(x.size()<k){
  8. auto iy=y.end();iy--;
  9. x.insert((*iy));
  10. s+=(*iy);
  11. y.erase(iy);
  12. }
  13. if(x.empty() || y.empty()){return;}
  14. while(1){
  15. auto ix=x.begin();
  16. auto iy=y.end();iy--;
  17. int ex=(*ix);
  18. int ey=(*iy);
  19. if(ex >= ey){break;}
  20. s+=(ey-ex);
  21. x.erase(ix);
  22. y.erase(iy);
  23. x.insert(ey);
  24. y.insert(ex);
  25. }
  26. }
  27. void add(int v){
  28. y.insert(v);
  29. balance();
  30. }
  31. void erase(int v){
  32. auto ix=x.find(v);
  33. if(ix!=x.end()){ s-=v; x.erase(ix); }
  34. else{ y.erase(y.find(v)); }
  35. balance();
  36. }
  37. int main(){
  38. int n;
  39. cin >> n >> k;
  40. vector<int> a(n,0);
  41. for(int i=0;i<k;i++){ x.insert(0); }
  42. for(int i=k;i<n;i++){ y.insert(0); }
  43. s=0;
  44. int q;
  45. cin >> q;
  46. while(q>0){
  47. q--;
  48. int p,w;
  49. cin >> p >> w;
  50. p--;
  51. add(w);
  52. erase(a[p]);
  53. a[p]=w;
  54. cout << s << "\n";
  55. }
  56. return 0;
  57. }
#include<bits/stdc++.h>

using namespace std;

int k;
multiset<int> x,y;
long long s;

void balance(){
  while(x.size()<k){
    auto iy=y.end();iy--;
    x.insert((*iy));
    s+=(*iy);
    y.erase(iy);
  }
  if(x.empty() || y.empty()){return;}

  while(1){
    auto ix=x.begin();
    auto iy=y.end();iy--;
    int ex=(*ix);
    int ey=(*iy);
    if(ex >= ey){break;}
    s+=(ey-ex);
    x.erase(ix);
    y.erase(iy);
    x.insert(ey);
    y.insert(ex);
  }
}

void add(int v){
  y.insert(v);
  balance();
}

void erase(int v){
  auto ix=x.find(v);
  if(ix!=x.end()){ s-=v; x.erase(ix); }
  else{ y.erase(y.find(v)); }
  balance();
}

int main(){
  int n;
  cin >> n >> k;
  vector<int> a(n,0);
  for(int i=0;i<k;i++){ x.insert(0); }
  for(int i=k;i<n;i++){ y.insert(0); }
  s=0;
  
  int q;
  cin >> q;
  while(q>0){
    q--;
    int p,w;
    cin >> p >> w;
    p--;
    add(w);
    erase(a[p]);
    a[p]=w;
    cout << s << "\n";
  }
  return 0;
}

投稿日時:
最終更新:



2025-04-05 (土)
22:21:42 +00:00