SRM 380 500pt: CompilingDecksWithJokers
問題概要
N(<50)種類のカードとジョーカーのあるカードがある。ジョーカーはJ(<5*10^8)枚あり、それ以外のカードはA[i](<5*10^8)枚ある。これから最大いくつのデッキを作ることができるか求める問題。デッキとは、異なるN種類のカード1組、または異なるN-1種類のカード+ジョーカーである。
考えたこと
- サンプルが弱くて参考にならん。取りあえず非自明なケースを突っ込んでおく。
- 単調性から二分探索臭がする。冷静に考えると別に二分探索で無くても解けそうな気はするが、判定問題に落とした方が問題が単純になるので二分探索で。
- target組のデッキを作ろうとすると、target以下の毎数しかないカードはジョーカーで補填する必要がある。で、補填した分は他のカードの枚数を減らさなくてはならない。
- カードを減らしすぎてもダメで、ジョーカーを使いすぎてもダメ、と言う風にすれば判定できる。
- 投げた。無事Accept。
practiceで通ったコード
計算量O(log(J+MAX_A) * N^2)。
#include <vector> using namespace std; typedef long long int64; struct CompilingDecksWithJokers { int maxCompleteDecks(vector <int> cards, int jokers) { int low = 0, high = 1<<30; while(high - low > 1){ int mid = (high + low)/2; if(check(cards, jokers, mid)){ low = mid; } else{ high = mid; } } return low; } bool check(vector<int> cards, int64 joker, int target){ //不足する分を埋めることができるか? const vector<int> original = cards; for(int i=0; i<(int)cards.size(); i++)if(target > original[i]){ int diff = target - original[i]; for(int j=0; j<(int)cards.size(); j++)if(i!=j){ cards[j] -= diff; if(cards[j] < 0){ return false; } } joker -= diff; } return joker >= 0; } };
計算量を落とす
- 判定部分の計算量をO(N)まで落とせる。
- 補填するのに必要な枚数を覚えておいて、その枚数がJ以下かつtarget以下ならOK。
- でも個人的にはO(N^2)のやつの方がわかりやすかった。
- 必要性は明らかだけど、十分性がしばらく分からなかった。
bool check(const vector<int>& cards, int joker, int target){ int64 offset = 0; for(int i=0; i<(int)cards.size(); i++){ offset += max(0, target - cards[i]); } return offset <= target && offset <= joker; }
おまけ
- デッキの定義を、N-1枚の全て異なるカードの組、とすればジョーカーを特別扱いする必要はなくなる。A = {10, 15}, J = 3はこのデッキの定義だと A = {10, 15, 3}と同じ。
- でもこういう出題のされ方をされると却って難しく見える。