Official

E - Patisserie ABC 2 Editorial by physics0523


この問題の解き方には色々な方針がありますが、そのうちの \(1\) つを紹介します。

まず、「綺麗さ」+「おいしさ」+「人気度」\(=X\) とし、 \(3\) 以上 \(3N\) 以下の各整数 \(k\) について、 \(X=k\) であるようなケーキがいくつあるかを求めます。
これは、 \(\{1,2, \dots , N\}\) から整数を \(1\) つ選ぶということを \(3\) 度繰り返し、選んだ整数の総和が \(k\) となる場合の数はいくつか、という問題の答えと一致します。
この問題は、dp[何回整数を選んだか][総和]={場合の数} という形の動的計画法で解くことができます。これを愚直に実装すると \(O(N^2)\) となってしまいますが、 \(dp[i][j]\) の遷移先が \(dp[i+1][j+1],dp[i+1][j+2], \dots dp[i+1][j+N]\) という区間になるので、累積和によって \(O(N)\) に高速化できます。

これにより、左から \(K\) 番目のケーキの \(X\) の値が分かりました。そこから先は、そのケーキの「綺麗さ」の値を仮定する \(O(N)\) のループによりこの問題を解くことができます。
具体的には、あるケーキの \(X\) の値と「綺麗さ」の値がわかれば、とりうる「おいしさ」の値はひとつの区間をなすので、その区間を「綺麗さ」ごとに求めていくようなループになります。詳しくは実装例も参照してください。

以上より、この問題を \(O(N)\) で解くことができました。

C++による実装例:

#include<bits/stdc++.h>

using namespace std;

int main(){
  int n;
  long long k;
  cin >> n >> k;
  long long dp[4][3000005]={0};
  dp[0][0]=1;
  for(int i=0;i<3;i++){
    for(int j=0;j<=i*n;j++){
      dp[i+1][j+1]+=dp[i][j];
      dp[i+1][j+n+1]-=dp[i][j];
    }
    for(int j=1;j<=(i+1)*n;j++){
      dp[i+1][j]+=dp[i+1][j-1];
    }
  }
  
  int x;
  for(int i=3;i<=3*n;i++){
    if(k<=dp[3][i]){x=i;break;}
    else{k-=dp[3][i];}
  }
  for(int i=1;i<=n;i++){
    int jmi=max(1,x-i-n);
    int jma=min(n,x-i-1);
    if(jmi>jma){continue;}
    if(k>(jma-jmi+1)){k-=(jma-jmi+1);continue;}
    int y=jmi+k-1;
    int z=x-i-y;
    cout << i << ' ' << y << ' ' << z << '\n';
    return 0;
  }
}

BONUS! この問題は、もう少し大きな制約でも解くことも、たくさんのケーキに対する問い合わせが来るような問題を解くこともできます。是非、いろいろな解法を検討してみてください。

posted:
last update: