教育的 Greedy 問題!
問題概要
個の整数からなる数列 と数列 がある。以下の操作を好きな回数だけ行える:
- 以下の整数 を 1 つ選び、 の値を ずつ増やす
すべての に対して が の倍数となるようにしたい。これを実現するための操作回数の最小値を求めよ。
制約
考えたこと
ひとまず問題を以下のように言い換えます。 数列 であって
- が の倍数
を満たすもののうち、 の最小値を求める問題と言い換えることができます。
後ろの方から前の方に行くにつれて、ドンドン差分が大きくなっていくイメージです。
Greedy
世の中には Greedy が成立する問題と、そうではない問題とがあります。その違いはどこにあるのか、この問題を通して考えてみます。
とりあえずこの問題でまず思い浮かぶのは、
- を増やせる操作は 全体への操作のみ
という点です。ですので、まずは の条件を満たすために、 全体への操作を何回するのが良いのかを考察してみます。
もし後先何も考えなくてよいのならば
- 以上の整数のうち の倍数でもある最小の整数を として、 回操作するのが良い
ということがわかります。さて、これは のことのみを考えた都合でしたが、数列全体のことを考えた時はどうでしょう???
一般には「今考えている要素にとって一番良い選択であっても、その後にとって良いとは限らない」という状況がほとんどです。代表例としてナップサック問題などがあります。しかし今回は
- 操作した後の が小さい方が、後の にとっても助かる
という状況になっています。理由は
- まで操作したあとの に対してできる操作は
- より小さな に対して まで操作したあとの に対しても実行できる
からです。つまり、 への操作回数が小さい方が、後先にとってもよい!!!!!自分にとっても、後続にとっても良い、となったらもう迷うことはないです。この種の考察が Greedy の構造になっているケースが多いように思います。
詰め
ここまでは について考えましたが残りも同様です
- が の倍数になる最小の をとる
- が の倍数で、なおかつ となるような最小の をとる
- が の倍数で、なおかつ となるような最小の をとる
- ...
という繰り返しをします。
#include <iostream> #include <vector> using namespace std; int main() { int N; cin >> N; vector<long long> A(N), B(N); for (int i = 0; i < N; ++i) cin >> A[i] >> B[i]; vector<long long> C(N+1, 0); // C[N] = 0 を番兵としておく for (int i = N-1; i >= 0; --i) { A[i] += C[i+1]; // とりあえず前回分を足しておく long long amari = A[i] % B[i]; long long need = 0; if (amari != 0) need = B[i] - amari; // 追加で必要な分 C[i] = C[i+1] + need; } cout << C[0] << endl; }