蟻本の練習問題埋め2-2(2)

貪欲法(その他)
ちょっとしんどくなってきた

POJ 2393 - Yogurt factory

http://poj.org/problem?id=2393

問題

N週間後までのヨーグルトの製造計画を立てる。
i週目にヨーグルトを1単位製造するのにCiセントかかる。
ヨーグルトは貯蔵庫に保存でき、1単位のヨーグルトを1週間保存するのにSセントかかる。
i週目にはYi単位のヨーグルトの需要がある。
全ての週で需要に答えるときコストの最小値はいくらか。

制約

N<=10000
Ci<=5000
S<=100
Yi<=10000

解法

いま、i週目の需要に答えることを考える。
j(<=i)週目にヨーグルトを製造し、それを貯蔵しておいたとき、1単位あたりにかかるコストAjは
Aj = Ci + S*(i-j)
である。
このとき、1単位あたりのコストがmin{Aj}となるような製造方法で需要に答えるのが最善となる。
Ajをiに関する1次関数と見ると、1次の係数は全て等しい。
よってiが変わってもAjの大小関係は変わらないので、最小のAjだけを保持しておけばいいことになる。
これによりO(N)で解ける。

POJ 1017 - Packet

http://poj.org/problem?id=1017

問題

6*6の正方形領域をいくつか用意し、1*1~6*6の正方形を詰め込む。
1*1~6*6の正方形を詰め込む個数がそれぞれ与えられるので、6*6の正方形領域が最低何個必要かを求めよ。

制約

特になし

解法

大きい順に詰め込んでいく。
大きい順に詰め込むとき、1*1と2*2を最大いくつ隙間に入れることが出来るかを記録しておく。
3*3の場合は、4で割った余りによって、1*1と2*2を隙間に入れることが出来る個数が変わる。

POJ 3040 - Allowance

http://poj.org/problem?id=3040

問題

N種類のコインがある。
それぞれのコインの価値は次に大きいコインの価値を割り切る。
それぞれのコインの枚数が与えられるので、そこからC以上の値段を払うことは最大で何回可能か。

制約

N<=20
C<=10^9
コインiの価値<=10^9
コインiの枚数<=10^6

解法

まず、1枚でC以上の価値を持つものは、1枚で払う。
基本的に価値が大きい順に払うが、超過する場合に無駄をしている可能性がある。
そこで、超過する一歩手前で止め、残っているコインのうち一番価値が小さいものを使ってC以上にする。
貪欲法は何故それで大丈夫なのかという説明が難しい…
強いて言えば、「それぞれのコインの価値は次に大きいコインの価値を割り切る」という制約より、価値が大きいコインから超過しないように払っていったとき、無駄な払い方をするようなことはないから。
すなわち、より価値が小さいコインを先に払ったとしても、せいぜい同じ金額に辿り着くのみ。
それなら出来るだけ、超過の時に無駄が少ない価値が小さなコインを残しておくべき、という考え。

ソース
int main() {
  int n, c;
  scanf("%d %d", &n, &c);
  ll ans = 0;
  vector<pii> v;
  REP(k,n) {
    int a, b;
    scanf("%d %d", &a, &b);
    if (a >= c) {
      // 1枚で超えるものはそれだけで払う
      ans += b;
    } else {
      v.push_back(pii(a,b));
    }
  }
  n = v.size();
  sort(ALL(v), greater<pii>());
  while(1) {
    vector<int> use(n,0);
    ll now = 0;
    // 大きいものからcを超えないように出来るだけ払う
    REP(i,n) {
      for (int j=0; j<v[i].second; ++j) {
        if (now + v[i].first > c) {
          break;
        }
        now += v[i].first;
        use[i]++;
      }
    }
    if (now < c) {
      // cに達していない場合、まだ残っている一番小さいもので払う
      // 超過しないように出来るだけ使っているはずなので、1枚付け足すだけで良い
      for (int i=n-1;i>=0;--i) {
        if (v[i].second-use[i]>0) {
          now += v[i].first;
          use[i]++;
          break;
        }
      }
    }
    if (now < c) break;         // 払えなかった
    // 同じ払い方を出来る限り繰り返す
    int a = INF;    // 同じ払い方が出来る最大回数
    REP(i,n) if (use[i]) a = min(a, v[i].second/use[i]);
    REP(i,n) v[i].second -= use[i]*a;
    ans += a;
  }
  cout << ans << endl;
}

POJ 1862 - Stripies

http://poj.org/problem?id=1862

問題

重さw[i]の生物がn匹いる。
重さaとbである2匹の生物が合体すると、重さは2*sqrt(a*b)となる。
最後に残る生物の重さの最小値を求めよ。

制約

n<=100
w[i]<=10000

解法

初めに合体するほどルートが沢山つくので、大きい順に合体させる。
大きいのは合体の度に調べれば良い。

POJ 3262 - Protecting the Flowers

http://poj.org/problem?id=3262

問題

N匹の牛が花を食べている。全て牛を小屋に戻すことにより、花の被害を最小限にしたい。
i番目の牛を小屋に戻すのには2*T[i]かかり、i番目の牛は1分にD[i]の花を食べる。
牛は1匹ずつしか小屋に戻すことはできない。
また、牛は小屋に戻され始めるまで花を食べ続ける。
最善の戻し方をしたとき、牛に食べられる花の最小値を求めよ。

制約

N<=10000
T[i]<=2000000
D[i]<=100

解法

牛aと牛bを比べたとき、どちらを先に小屋に戻すべきかを考える。
牛aと牛b以外の牛のD[i]の合計をSとするとき、食べられる花の量は

  • 牛iを先に小屋に戻す場合

 2*T[a] * (S + D[b]) + 2*T[b]*S --- (1)

  • 牛jを先に小屋に戻す場合

 2*T[b] * (S + D[a]) + 2*T[a]*S --- (2)
となる。
(1)<(2)すなわち、T[a]*D[b]