CODE FESTIVAL 2016 qual B Greedy customers(700)
問題
要素数の数列に対して、以下の操作を行う。
操作: 以下の正の整数を選び、初めて以上になるような要素からを引く。ただしこの操作によって要素がになってはいけない。
この操作を最大で何回行えるか。
制約
解法の概要
前から順番に操作を行っていく。それより前の最大値がのとき、ならば回操作を行う。ならば を に更新する。
考察
1. 前から順番に操作を行っていくとしてよい
連続する2回の操作に注目します。この2回の操作の対象のインデックスをそれぞれとします。だった場合、操作の順番を入れ替えられることが(図を書くと)分かります。また、この2回の操作による結果は変わりません。よってバブルソートの要領で、操作の対象を広義単調増加にすることができます。したがって前から順番に操作を行う場合だけを考えれば良いです。
2. 後のことを考えるとできるだけ小さくするのが良い
あるところまで操作したときの累積maxがであった場合と、であった場合を考えます。累積maxがであったときにそれより後ろに対して行う操作は、累積maxがであるときにも行うことができます。つまり、累積maxがのときの、それより後ろに対して行える操作列の集合は、累積maxがのときに行える操作列の集合を含んでいます。よって要素を小さくすることで(累積maxが大きくなることはないので)、それより後ろに対する結果が悪くなることはないです。
3. 1,2を踏まえてやってみる
2よりとするのが良いです。操作回数の上界はで、達成可能です。次に以降を見ていきます。でなければならないことと、以上残さないといけないことから、操作回数の上界はです。これはが出てくるまでは、累積maxを変えずに達成することができます(1回以上操作できる場合は、を回引いてから、を引くことで1にできます)。が出てきた場合は、累積maxがに変わります。それ以降はが出てくるまで、同様にして累積maxを変えずに上界を達成することができます。これを繰り返すことで、各場所で上界を達成しているので、最適解を得ることができます。
実装
ACコード
#include<bits/stdc++.h> using namespace std; using Lint=long long; int main() { int N; cin>>N; vector<int> a(N); for(int i=0;i<N;i++) cin>>a[i]; Lint ans=a[0]-1; int mx=1; for(int i=1;i<N;i++){ if(a[i]==mx+1){ mx++; }else{ ans+=(a[i]-1)/(mx+1); } } cout<<ans<<endl; return 0; }
まとめ
- 操作の順番を入れ替えてみる
- 現在最適な選択をとりつつ、あとの選択肢が減らないように(?)できるならば貪欲にする
- 最大値を求める問題で、上界を達成できるか考える
感想
慣れていないので記事を書くのが大変でした(おかしなところがありそう(あります))。証明が書けません。