2014 Yandex.Algorithm Elimination Stage, Round 2 B - Remainders
解法
ある数 a_i からスタートした時に,それ以上の値で余りをとっても変化はない.
したがって,数列をまずソートし,最初にする数字を何にするか全部試すのが良さそうである.
これは dp で計算できて,
dp[i] := 大きい方から i 番目まで見た時に,あまりとしてありえる数字の集合
とすると,
dp[i] = dp[i - 1] + (dp[i - 1] に含まれる数を a[i] で割った余り) + a[i]
となる(+ は集合の和とする).
dp[i - 1] の部分についてだが,途中で a_j で割った後にそれ以上の値 a_k で割っても変化がないという議論のとおり,a_i を最初に選んだ後,それ以下の数字 a_j を次に選んだとすれば,a_j <= a_k <= a_i となる a_k は無視してよくなる.
これをあらわすのが dp[i - 1] の部分である.
あとの2項はそれはそう.
最後に,今できている数字の集合のうち,数列の最小の値以下のものをカウントすると答えになる.以下である理由は,最小値を一番最初の数に選べば,ずっと最後まで残るからである.
ただし,数列に含まれる最小値が複数ある場合は,その値自体を取ることが不可能であるので,その場合は未満でカウントする.
計算量は O(N * max(a[i])) である.
ソースコード
#include <bits/stdc++.h> using namespace std; int main() { int n; cin >> n; vector<int> a(n); for(int i = 0; i < n; ++i) { cin >> a[i]; } sort(rbegin(a), rend(a)); unordered_set<int> dp; for(int i = 0; i < n; ++i) { unordered_set<int> nxt = dp; nxt.insert(a[i]); for(auto x : dp) { nxt.insert(x % a[i]); } dp = move(nxt); } int ans = 0; const int lim = a.back() == a[a.size() - 2] ? a.back() - 1 : a.back(); for(auto x : dp) { ans += x <= lim; } cout << ans << endl; }
感想
すぐに書けたけど,よく考えるとうまくできてる問題だなあ.