配点: 400 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 659
成人祝い...宇宙人...星人... うーん、妙だな。*1 *2
問題概要
ある星には 人の宇宙人がおり、全員未成年である。
人目の宇宙人は現在
個の石を所持しており、ちょうど
年後に成人する。
この星では誰かが成人するとき、石を1個以上所持している成人全員が、成人する宇宙人に成人祝いとして石を1個渡す。このとき、 年後に各宇宙人が所持している石の個数を求めよ。ただし、今後新たな宇宙人は産まれないものとする。
制約
- 入力はすべて整数。
考察
簡単のため、以下では 人目の宇宙人を単に「宇宙人
」と表記する。
まず、問題文の読み替えを行おう。成人時に「自身より年下(左側)の宇宙人から石をもらう」のではなくて、「自身より年上(右側)の宇宙人に対して可能な限り渡す」と考える。つまり、
- 宇宙人
が成人したとき、宇宙人
へ自分の石がなくなるまで石を渡していく
ということ。こうすることで、石の移動を区間で考えやすくなったことが分かるだろうか。
宇宙人 が成人時に宇宙人
に石を渡すとする。ここで、
である。
石を各々に1個ずつ渡す操作は「 に1加算する」というようにしてもいいのだが、これでは全体の計算量が
となってしまい、 TLE になる。
そこで、 imos 法により高速化を試みる。具体的には以下の通り。なお、ここからは 0-indexed での説明となる。
宇宙人
が受け取る石の合計
imos 法のテーブル
を用意し、 の順に次の処理を行う。
のとき(宇宙人
は石を渡されることはない)、
として(
までの累積和を取った)、
に加算する。 ※自身より左の宇宙人が受け取れる石の数はこれ以上変化しないので、累積和を逐次取ってしまって構わない。
として、
から減算する。
をインクリメント、
をデクリメントする(imos 法テーブルの更新)。
こうすることで、全体の計算量を (定数倍を無視すると
)にまで落とすことができた。
実装例
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) // ======================================== // int main() { int N; cin >> N; vector<int> A(N); rep(i, 0, N) cin >> A[i]; vector<int> diff(N + 1, 0), get_stones(N, 0); rep(i, 0, N) { if (i > 0) { get_stones[i] = get_stones[i - 1] + diff[i]; A[i] += get_stones[i]; } int give_stones = min(A[i], N - i - 1); A[i] -= give_stones; diff[i + 1]++; diff[min(N, i + 1 + give_stones)]--; } rep(i, 0, N) { cout << A[i] << " "; } cout << endl; return 0; }
実装時間: 15分
コメント
imos 法によるきれいな高速化が可能な問題だった。
ちなみに Segment Tree で解くこともできるらしい (未実装) 。