
配点: 475 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: 1298 / NoviSteps: 1D
問題概要
長さ の整数列
が与えられる。これから、以下の操作を繰り返し行うことにより、
のすべての長さ
の連続部分列についてその総和が
の倍数であるようにしたい。
なる整数
を選び、
の値を
増やす。
このとき、操作回数として考えられる最小値を求めよ。
制約
- 入力される値はすべて整数
考察
条件の言い換え
まず、「 のすべての長さ
の連続部分列についてその総和が
の倍数である」という条件を言い換えよう。
連続部分列の初項を としたとき、とりあえず
である必要がある。
また、この初項を1つ右にずらしたとしても、 が成り立たなければならない。
ここで、1つ目の式から2つ目の式を引くと、
が得られる。
これは、「インデックスが 離れた
の要素同士は、
による剰余が等しくなければならない」と解釈できる。
したがって、 の初めの
個の要素それぞれについて、
による剰余をどの値にするか(操作回数)を決めたら、残りの
個の要素に対する操作回数も自動的に確定することになる。
A の各要素をグループ分けして、コストテーブルを前計算する
上で言い換えた条件を基に、 の各要素を以下のようにグループ分けすることを考える。
- グループ
:
- グループ
:
...
- グループ
:
グループ の要素について、
による剰余を
に統一することにする。
ただし、 である。
このとき、
を
による剰余を全て
にするために必要な操作回数(コスト)の最小値
と定義すれば、これは
- ただし、
- ただし、
と計算できる。操作では、要素に加算することしかできないので、 のときは追加で
を加算する必要があることに注意しよう。
動的計画法で答えを求める
さて、ここまでの議論を踏まえると、我々がやりたいのは
個にグループ分けしたグループ内で、統一する
による剰余の値をそれぞれ決めていく。ただし、以下の条件を満たすように決める。
- 決めた剰余の値の合計が
の倍数になる。
- 操作回数が最小になる。
- 決めた剰余の値の合計が
ということである。グループ毎に統一する による剰余の値を逐次的に決めていけるので、これは動的計画法の出番だ。
DPテーブルは以下のように定義できる。
個目のグループまでについて、統一する
による剰余の値を決めたとき、その剰余の和を
で割った余りが
となるような組み合わせの中での、コストの合計の最小値
初期値は であり、他は全て
とできる。
グループ までが確定していて、新たにグループ
で
による剰余の値を
に統一すると決めたなら、コストは
必要であり、グループ
までの「統一する剰余の値の和を
で割った余り」は、
である。
したがって、遷移式は以下のようになる。
求める答えは を参照すればよい。
計算量について。
コストテーブルは の二重ループ内で、要素数約
個に対してのループを行っているので、
と評価できる。
また、DPテーブルは で計算が可能である。
以上から、この問題を で解くことができた。
実装例
#include <bits/stdc++.h> using namespace std; #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) constexpr int INF = 1e+9; template <typename T> inline bool chmin(T &a, T b) { return ((a > b) ? (a = b, true) : (false)); } // ======================================== // int main() { int N, M, L; cin >> N >> M >> L; vector<int> A(N); rep(i, 0, N) cin >> A[i]; vector<vector<int>> cost(L, vector<int>(M, 0)); rep(i, 0, L) rep(j, 0, M) { int sum = 0; for (int k = i; k < N; k += L) { sum += (j - A[k] + M) % M; } cost[i][j] = sum; } vector<vector<int>> dp(L + 1, vector<int>(M, INF)); dp[0][0] = 0; rep(i, 0, L) rep(j, 0, M) rep(k, 0, M) { chmin(dp[i + 1][(j + k) % M], dp[i][j] + cost[i][k]); } cout << dp[L][0] << endl; return 0; }
実装時間: 90分
コメント
難しい。
「 の各要素を
個ごとにグループ分けして、コストテーブルを前計算する」ところまで出来れば、そこからDPの漸化式を考えるのは割とスムーズに行けるかもしれない。