競プロ覚書

思考整理

ABC200-E Patisserie ABC 2

atcoder.jp
ケーキをパラメータの合計値でグループ分けする、という考察までは進んだが、そこで詰まった。


【問題】
1<=a,b,c<=Nである(a,b,c)の数列が合計値->各パラメータの昇順に並んでいる。K番目の(a,b,c)の値を出力せよ。
【典型ポイント】
辞書順K番目を求める問題は、各パラメータごとにグループ分けして考える(よくDPが使える)
【知らなかったこと】
一様の値を一定区間に連続して配る時は、DPだろうがimos法が使える
【解法1】
まず、K番目のa+b+cの合計値を知りたい。
これは、DP + imos法 で求められる。
dp[i][j]:先頭からi個のパラメータを決めたときの、パラメータの合計値がjである場合の数 とすると
遷移式:dp[i+1][j+(1~N)] += dp[i][j] 
愚直に遷移するとO(N^2)かかってしまうので、一様の値を一定範囲に配ることに着目する。
imosで高速遷移を行うとdpテーブルの計算量がO(N)になって間に合う

K番目の合計値は, i=3から昇順にdp[3][i]をKから引いていき、次に引いた場合Kが0以下になる最初のiとなる。
これでKの合計値が求まった。

あとはa,b,cの値を順次確定させていく。
合計値を求めるのと同じようにa,bを昇順にチェックし、どのグループに属しているかを調べる。

【解法2】
立方体を書いてK番目の合計値を求める方法もある。(自分には使いこなせなさそう)
Nを制限しないとき、a+b+cの場合の数の増え方には規則性がある(等差数列っぽい)のだが、図にあらわすとわかりやすい。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using pll = pair<long long, long long>;
constexpr char ln =  '\n';
constexpr long long MOD = 1000000007;
constexpr long long INF = 1000000000 + 100;
constexpr long long LINF = 1000000000000000000 + 100;
#define all(v) v.begin( ), v.end()
#define rep(i,n) for(int i=0;i<(n);i++)
#define rept(i, j, n) for(int i=(j); i<(n); i++)
#define rrep(i, n) for(int i=(n); i>=0; i--)
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

//max(sum(a,b,c))
const int MAX_SUM = 1e6*3+100;

ll dp[4][MAX_SUM];
int main(){
    ll N, K; cin >> N >> K;

    //dp[i][j]:先頭からi番目までの数字を決定したときの、合計値がjである場合の数
    dp[0][0] = 1;
    rep(i, 3){
        rep(j, MAX_SUM){
            if(dp[i][j]==0)continue;
            //imos 前処理
            dp[i+1][j+1] += dp[i][j];
            dp[i+1][j+N+1] -= dp[i][j];
        }
        //imos 同一テーブル上でメモから値を導く
        rept(j,1, MAX_SUM){
            dp[i+1][j] = dp[i+1][j] + dp[i+1][j-1];
        }
    }

    int sum = -1;
    for(int i=3; i<=MAX_SUM; i++){
        if(K <= dp[3][i]){
            sum = i;
            break;
        }
        K -= dp[3][i];
    }
    int ra = -1;
    for(int i=1; i<=N; i++){
        if(K <= dp[2][sum-i]){
            ra = i;
            break;
        }
        K -= dp[2][sum-i];
    }
    int rb = -1;
    for(int i=1; i<=N; i++){
        if(K <= dp[1][sum-ra-i]){
            rb = i;
            break;
        }
        K -= dp[1][sum-ra-i];
    }
    cout << ra << " " << rb << " " << sum-ra-rb << ln;
}