結果
問題 |
No.2025 Select $k$-th Submultiset
|
ユーザー |
![]() |
提出日時 | 2025-02-10 21:47:30 |
言語 | C++17(gcc12) (gcc 12.3.0 + boost 1.87.0) |
結果 |
AC
|
実行時間 | 97 ms / 2,000 ms |
コード長 | 6,431 bytes |
コンパイル時間 | 3,408 ms |
コンパイル使用メモリ | 114,096 KB |
実行使用メモリ | 12,672 KB |
最終ジャッジ日時 | 2025-02-10 21:47:41 |
合計ジャッジ時間 | 10,128 ms |
ジャッジサーバーID (参考情報) |
judge2 / judge4 |
(要ログイン)
ファイルパターン | 結果 |
---|---|
sample | AC * 3 |
other | AC * 42 |
ソースコード
// ChatGPT o3-mini-highのコード #include <iostream> #include <vector> #include <algorithm> using namespace std; const long long INF = 1000000000000000001LL; int main(){ ios::sync_with_stdio(false); cin.tie(nullptr); int N; long long L; cin >> N >> L; // 1-indexed: c[1]~c[N] vector<long long> c(N+1); for (int i = 1; i <= N; i++){ cin >> c[i]; } // 特にN==1の場合の処理 if(N == 1){ int Q; cin >> Q; for (int qi = 0; qi < Q; qi++){ long long k; cin >> k; // Sは数字1のみからなり、c[1]個ある。 // 長さLの部分列が存在するのは L <= c[1] で、可能な部分列はただ一通り(L個の1)。 if(L <= c[1] && k == 1){ cout << L << "\n"; } else { cout << -1 << "\n"; } } return 0; } // Smax[i] = c[i] + c[i+1] + ... + c[N] を計算(i=1~N, かつ Smax[N+1]=0) vector<int> Smax(N+2, 0); Smax[N+1] = 0; for (int i = N; i >= 1; i--){ Smax[i] = c[i] + Smax[i+1]; } // dp[i][u] = 「数字 i~N から、選ぶ個数の和が u となる採り方の個数」 // ※ dp[i]の添字は 0~Smax[i] まで。ここでは i=2~N の分を前計算する(digit1はクエリ処理中に貪欲に決定) vector< vector<long long> > dp(N+2); vector< vector<long long> > cum(N+2); // dpの累積和 // i = N の場合: Sに数字Nが c[N] 個あるので、部分列として選べる個数は0~c[N]のみで、 // 各 u (0<=u<=c[N]) で採り方は1通り、その他は0通り dp[N].resize(Smax[N] + 1, 0); cum[N].resize(Smax[N] + 1, 0); for (int u = 0; u <= c[N]; u++){ dp[N][u] = 1; } // 累積和 cum[N] cum[N][0] = dp[N][0]; for (int u = 1; u <= Smax[N]; u++){ long long tmp = cum[N][u-1] + dp[N][u]; if(tmp > INF) tmp = INF; cum[N][u] = tmp; } // i = N-1 から 2 まで:dp[i][u] = sum_{x=max(0, u-Smax[i+1])}^{min(c[i], u)} dp[i+1][u-x] for (int i = N-1; i >= 2; i--){ dp[i].resize(Smax[i] + 1, 0); cum[i].resize(Smax[i] + 1, 0); for (int u = 0; u <= Smax[i]; u++){ // digit i から選ぶ個数 x の候補は、 // x ∈ [max(0, u - Smax[i+1]), min(c[i], u)] int x_low = max(0, u - Smax[i+1]); int x_high = min((int)c[i], u); if(x_low > x_high){ dp[i][u] = 0; continue; } // x を変数変換: w = u - x とすると、wは u - x_high から u - x_low まで int Lw = u - x_high; int Rw = u - x_low; // dp[i+1] は添字 0~Smax[i+1] なので、Rwは Smax[i+1] を超えないはずですが安全のため if(Rw > Smax[i+1]) Rw = Smax[i+1]; long long ways = 0; if(Lw == 0) ways = cum[i+1][Rw]; else ways = cum[i+1][Rw] - cum[i+1][Lw - 1]; if(ways > INF) ways = INF; dp[i][u] = ways; } // dp[i] の累積和 cum[i] cum[i][0] = dp[i][0]; for (int u = 1; u <= Smax[i]; u++){ long long tmp = cum[i][u-1] + dp[i][u]; if(tmp > INF) tmp = INF; cum[i][u] = tmp; } } // クエリ処理:各クエリで、Sから長さLの部分列(同じ列は重複して数えない)の中で、 // 辞書順(「d1, d2, ..., dN」について、d1が大きいほど小さい、d1が同じならd2が大きいほど小さい、…)で // k番目のものを求め、その各数字の出現個数 d[1]...d[N] を出力する。 // ※ digit1 については、残りの採用個数 rem と、digit2~Nの通り数(dp[2])から貪欲に決定する int Q; cin >> Q; for (int qi = 0; qi < Q; qi++){ long long k; cin >> k; vector<long long> d(N+1, 0); // 1-indexed: d[1]...d[N] long long rem = L; // 残り、digit2~N で埋めるべき個数も含めた全体の採用個数 bool ok = true; // digit1~digit(N-1)について決定 for (int i = 1; i <= N-1; i++){ // digit i で選べる個数 x は、 // x ∈ [lb, ub] で lb = max(0, rem - Smax[i+1]), ub = min(c[i], rem) int lb = max(0, (int)(rem - Smax[i+1])); int ub = min((int)c[i], (int)rem); if(lb > ub){ ok = false; break; } // x と置換して u = rem - x とおくと、 // x が大きい(= digit i を多く使う)ほど u は小さくなるので、候補 u は [rem-ub, rem-lb] となる。 int L0 = rem - ub; int R0 = rem - lb; // dp[i+1] の累積和 cum[i+1](添字0~Smax[i+1])を用いて、 // 区間 [L0, U] の和が k 以上となる最小の U を二分探索する int lo_bin = L0, hi_bin = R0, U_found = R0 + 1; while(lo_bin <= hi_bin){ int mid = (lo_bin + hi_bin) / 2; long long sumInterval = (L0 == 0 ? cum[i+1][mid] : cum[i+1][mid] - cum[i+1][L0 - 1]); if(sumInterval >= k){ U_found = mid; hi_bin = mid - 1; } else { lo_bin = mid + 1; } } if(U_found > R0){ ok = false; break; } // 対応する x = rem - U_found int chosen_x = rem - U_found; d[i] = chosen_x; // さらに、[L0, U_found-1] の累積和分だけ k を減らす long long sumIntervalBefore = 0; if(U_found - 1 >= L0){ sumIntervalBefore = (L0 == 0 ? cum[i+1][U_found - 1] : cum[i+1][U_found - 1] - cum[i+1][L0 - 1]); } k -= sumIntervalBefore; rem -= chosen_x; } // 最後、digit N では d[N] = rem でなければならない(ただし rem <= c[N] である必要がある) if(rem < 0 || rem > c[N]) ok = false; else d[N] = rem; if(!ok || k != 1) { cout << -1 << "\n"; } else { for (int i = 1; i <= N; i++){ cout << d[i] << (i == N ? "\n" : " "); } } } return 0; }