Opis
BOI 2016
Bosses
Park
Spiral
Cities
Maze
Swap
CEOI 2017
Skierowanie dróg
Pewny zakład
Pułapka Mariusza
Budujemy mosty
Pościg Mariusza
Palindromiczny podział
BOI 2018
Miłosny wielokąt
Marsjańskie DNA
Problemy dżdżownicy
Prąd przemienny
Genetyka
Ścieżki
BOI 2019
Flash Memory
Nautilus
Alpine Valley
Tom's Kitchen
Necklace
Olympiads
BOI 2020
Kolory
Mieszanki
Joker
Graf
Wioska
Wirusy
CEOI 2020
Fajny płot
Drogi
Star Trek
Napój Wielkiej Potęgi
Wiosenne porządki
Szachowa gorączka

Tom's Kitchen

Treść zadania

Mamy do przygotowania n potraw, i-ta wymaga co najmniej ai godzin pracy. Możemy zatrudnić m kucharzy, i-ty będzie pracował co najwyżej bi godzin, ale musimy zapłacić mu dokładnie bi. Nad każdą potrawą musi pracować co najmniej k kucharzy.

Obliczyć minimalną ilość pieniędzy, którą musimy zapłacić za czas, w którym kucharze nie będą pracować.

Przypadek dla k=1

W podzadaniu 3 mamy k=1, czyli nie musimy się przejmować liczbą kucharzy przygotowujących potrawę, wystarczy, że i-tej z nich poświęcimy ai godzin. Zatem na wszystkie potrawy będziemy potrzebowali A=1inai godzin. Jeśli zatrudnimy podzbiór kucharzy S, to będą oni pracować BS=iSbi godzin.

Tak więc wystarczy, żeby BSA, przy czym chcemy zminimalizować wartość BSA. Wykorzystamy tutaj rozwiązanie problemu plecakowego. Trzymamy tablicę dp[0..B] dla B=1imbi. Analizujemy kolejnych kucharzy i chcemy, by po przeanalizowaniu i-tego był spełniony niezmiennik, że dp[j]=1, jeśli istnieje podzbiór kucharzy S{1,,i}, że BS=j.

Na koniec odpowiedzią jest jA dla najmniejszego jA, że dp[j]=1. Poniższy kod działa w złożoności czasowej O(mB):

const int N = 310;
int n,m,k,a[N],b[N];
int dp[N*N],suma,sumb;

int main() {
  scanf("%d%d%d",&n,&m,&k);
  REP(i,n) scanf("%d",&a[i]);
  REP(i,m) scanf("%d",&b[i]);

  REP(i,n) suma += a[i];
  REP(i,m) sumb += b[i];
  
  dp[0] = 1;
  REP(i,m) {
    for (int j = sumb-b[i]; j >= 0; --j) {
      dp[j + b[i]] |= dp[j];
    }
  }

  int ans = -1;
  for (int j = sumb; j >= suma; --j) {
    if (dp[j]) ans = j - suma;
  }

  if (ans == -1) printf("Impossible");
  else printf("%d\n", ans);
}

Pełne rozwiązanie

W ogólnym przypadku musimy jeszcze zagwarantować, żeby każda potrawa była przygotowywana przez co najmniej k kucharzy. Ponieważ kucharze mogą zmieniać potrawy o pełnych godzinach, więc w szczególności każda potrawa musi wymagać co najmniej k godzin (więc jeśli mamy jakieś ai<k, to od razu można przerwać program z odpowiedzią Impossible).

Dla każdego kucharza i każdej potrawy oznaczmy pierwszą godzinę, przez którą ten kucharz pracuje nad tą potrawą jako „specjalną”. Zatem musimy przydzielić dokładnie nk godzin specjalnych. Widać też, że i-ty kucharz może obsłużyć co najwyżej min(bi,n) godzin specjalnych.

Zatem dla podzbioru kucharzy S możemy obliczyć GS=iSmin(bi,n) jako maksymalną liczbę godzin specjalnych, które mogą być obsłużone przez podzbiór S. Więc w poprawnym rozwiązaniu musi być spełnione GSnk.

Okazuje się, że jest to warunek wystarczający (bo konstruując przypisanie potraw kucharzom z podzbioru wystarczy na początku przydzielić godziny specjalne, a potem resztę jakkolwiek).

Tak więc chcemy znaleźć taki podzbiór S, żeby BS było minimalne, BSA oraz GSnk. Możemy w tym celu zmodyfikować nasze rozwiązanie problemu plecakowego, przez obliczanie dp[j] jako maksymalna możliwa liczba godzin specjalnych GS dla pewnego podzbioru kucharzy S{1,,i}, że BS=j.

Poniższa modyfikacja również działa w złożoności czasowej O(mB):

  REP(i,n) if (a[i] < k) {
    printf("Impossible\n");
    return 0;
  }
  
  REP(i,sumb+1) dp[i] = -infty;
  dp[0] = 0;

  REP(i,m) {
    for (int j = sumb-b[i]; j >= 0; --j) {
      dp[j + b[i]] = max(dp[j + b[i]], dp[j] + min(b[i], n));
    }
  }

  int ans = -1;
  for (int j = sumb; j >= suma; --j) {
    if (dp[j] >= n*k) ans = j - suma;
  }