BOJ 26969 - Bribing Friends

image.png

간만에 DP한테 뚜드리맞은 문제


Knapsack인것과 $x_i$ 로 정렬하여 항상 $x_i$ 가 작은것에 cone을 쓰는것이 최적임은 관찰했지만 거기서 막혀서 에디토리얼을 참고했다.

우리가 $M$ 개의 정답이 될 소를 안다고 하자.

이 소들에서는 $x_i$ 가 작은것에 cone을 쓰는것이 최적의 효율을 보이므로 최적이다.

$x_i$ 로 정렬하고

$dp[N][A+B]$ 를 정의한다.

$dp[i][j]=i$ 번 째 소를 볼 차례이고, 현재 남은 mooney와 cone의 합이 $j$ 일 때, 지금까지 얻은 $P$의 최대합

$dp$값을 모두 방문하지 않음으로 초기화하고 $dp[1][A+B]=0$ 으로만 초기화한다.

이제 순회하며 아직 방문하지 않은 상태면 넘기고 그렇지 않다면 현재 소를 얻을 수 있는지 판단하고 $i+1$의 상태들을 업데이트 해준다.

$j$ 가 있을 때, 현재 남은 mooney는 $\text{Min}(A, j)$ 이고 cone은 $\text{Max}(0, j - A)$ 이다.

$x_i$가 오름차순 정렬되어있으므로 무조건 cone을 먼저 쓰는것이 최적의 해를 구할 수 있고 현재 남은 cone으로만 현재 소를 친구로 만들 수 있다면

$dp[i+1] \left[j- \left\lfloor \dfrac{\text{cone}}{x_i} \right\rfloor \cdot \text{cone}\right]$ 을 업데이트 해줘야 한다.

현재 남은 cone으로만 현재 소를 친구를 만들 수 없다면 일단 모든 cone을 다 쓰고 mooney를 써서 친구로 만들 수 있는지 봐야 한다.

$\text{mooney} \ge c_i-\left\lfloor \dfrac{\text{cone}}{x_i} \right\rfloor$ 인지 확인한다음에 그렇다면

$dp[i+1]\left[ \text{mooney} -c_i+\left\lfloor \dfrac{\text{cone}}{x_i} \right\rfloor \right]$ 를 업데이트 해줌이 올바르다.


개념은 어렵지 않아도 구현이 개인적으로 잘 짜야돼서 까다롭다.

반복문 DP를 많이 연습하지 않으면 흔히 발생하는 현상인데 초기값은 어떻게 설정해야 할지 현재 상태를 기반으로 다음 상태를 먼저 업데이트 할지, 이전값들을 이용해서 현재값을 업데이트할 지 잘 결정해야 한다. 이 문제는 전자의 구현이 더 알맞다.

여기서 알맞음의 기준은 현재의 상태를 이용해서 다음 상태들의 값을 업데이트 해주는 것이 더 편한 경우이다.

const int MAX = 2000;  
int dp[MAX + 5][MAX * 2 + 5];  
  
struct F {  
   int p, c, x;  
};  
  
void solve() {  
   int N, A, B;  
   cin >> N >> A >> B;  
  
   vector<F> f(N + 1);  
  
   for (int i = 1; i <= N; i++) {  
      cin >> f[i].p >> f[i].c >> f[i].x;  
   }  
   sort(f.begin() + 1, f.end(), [&](auto &a, auto &b) { return a.x < b.x; });  
  
   memset(dp, -1, sizeof dp);  
   dp[1][A + B] = 0;  
   for (int i = 1; i <= N; i++) {  
      for (int j = 0; j <= A + B; j++) {  
         if (dp[i][j] == -1) continue;  
         dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);  
  
         int mooney = min(A, j);  
         int cone = max(0, j - A);  
         int cone_need = f[i].x * f[i].c;  
         if (cone >= cone_need) {  
            dp[i + 1][j - cone_need] = max(dp[i + 1][j - cone_need], dp[i][j] + f[i].p);  
         } else {  
            int mooney_need = f[i].c - cone / f[i].x;  
            if (mooney_need <= mooney) {  
               dp[i + 1][j - cone - mooney_need] = max(dp[i + 1][j - cone - mooney_need], dp[i][j] + f[i].p);  
            }  
         }  
      }  
   }  
   cout << *max_element(dp[N + 1], dp[N + 1] + A + B + 1);  
}

Tags:

Categories:

Updated:

Comments