教育的 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; }