[Node.js] 백준 19941 - 햄버거 분배

함부기 함부기 함부기 함부기 여봐라~

햄버거를 최대한 많이 먹는 경우를 찾는 문제.

접근

식탁 위에 사람(P)햄버거(H)가 일렬로 놓여 있다. 각 사람은 자신의 위치로부터 K 거리 이내에 있는 햄버거를 단 하나만 먹을 수 있다. 최대한 많은 사람이 햄버거를 먹도록 하는 것이 목표.

=> 전형적인 그리디(Greedy) 알고리즘 문제의 형태. 각 단계에서 최선의 선택을 하여 전체 최적해를 도출하자.

인데!

가까이 있는걸 먹는게 정답은 아니라는.. 나중에 이 고민 나옴

첫 시도

  1. 사람을 앞에서부터 처리
  2. 자기 범위 내 가장 가까운 햄버거를 먹는다 = 햄버거가 많이 남으니 남들도 많이 먹을 수 있을 것.

코드

// ... (입력 처리 생략) ...

for (let i = 0; i < N; i++) {
  // 각 사람(P)을 왼쪽에서 오른쪽으로 처리
  if (table[i] === "P") {
    let isEaten = false;

    // 1. 왼쪽 K 범위 내에서 가장 가까운 햄버거부터 탐색 (j = 1 부터 K 까지 증가)
    for (let j = 1; j <= K; j++) {
      const leftIdx = i - j;
      if (leftIdx < 0) continue;
      if (table[leftIdx] === "H" && taken[leftIdx] === false) {
        taken[leftIdx] = true;
        ans++;
        isEaten = true;
        break;
      }
    }

    // 2. 왼쪽에서 못 찾으면, 오른쪽 K 범위 내에서 가장 가까운 햄버거부터 탐색 (j = 1 부터 K 까지 증가)
    if (!isEaten) {
      for (let j = 1; j <= K; j++) {
        const rightIdx = i + j;
        if (rightIdx >= N) continue;
        if (table[rightIdx] === "H" && taken[rightIdx] === false) {
          taken[rightIdx] = true;
          ans++;
          break;
        }
      }
    }
  }
}
// ... (출력 생략) ...

처음에는 입력 문제나, 사소한 인덱스 오류일 것이라 생각하고 3회정도 더 제출했으나 결국 실패.

새로운 가설과 정답

  1. 가장 멀리 있는 (왼쪽부터 보니까 왼쪽은 가장 멀리 있는 것)
  2. 오른쪽은 가장 가까운 것 (다음 사람에게는 왼쪽 햄버거가 되므로)

생각한 이유

  1. 왼쪽에서 가장 멀리 있는 햄버거를 먼저 집는다면, 내가 아니면 아무도 먹지 못할 햄버거이므로, “많이” 라는 조건에 맞다고 생각함

  2. 만약 왼쪽에 햄버거가 없다면, 오른쪽에서 가장 가까운 햄버거를 집는다. == 가장 효율적인 선택.”

코드

const fs = require("fs");
const input = fs
  .readFileSync("/dev/stdin")
  .toString()
  .trim() // '\r' 제거를 잊지 않았습니다!
  .split("\n");

const [N, K] = input[0].split(" ").map(Number);

const table = input.slice(1)[0].split("");
const taken = Array(N).fill(false); // 햄버거 집힘 여부 배열, 올바르게 초기화

let ans = 0; // 햄버거를 먹은 사람 수

for (let i = 0; i < N; i++) {
  // 각 사람(P)을 왼쪽에서 오른쪽으로 처리
  if (table[i] === "P") {
    let isEaten = false; // 현재 사람이 햄버거를 먹었는지 여부

    // 1. 왼쪽 K 범위 내에서 가장 먼 햄버거부터 탐색
    // j를 K부터 1까지 감소시키면서 현재 위치(i)에서 왼쪽으로 j칸 떨어진 곳을 확인
    for (let j = K; j >= 1; j--) {
      // K부터 1까지 감소!
      const leftIdx = i - j; // 왼쪽 햄버거의 인덱스
      if (leftIdx < 0) continue; // 범위 밖이면 건너뜀

      // 햄버거('H')이고 아직 집히지 않았다면
      if (table[leftIdx] === "H" && taken[leftIdx] === false) {
        taken[leftIdx] = true; // 햄버거를 집음
        ans++; // 카운트 증가
        isEaten = true; // 먹었음을 표시
        break; // 햄버거를 찾았으니 더 이상 탐색할 필요 없음
      }
    }

    // 2. 왼쪽에서 햄버거를 찾지 못했다면, 오른쪽 K 범위 내에서 가장 가까운 햄버거부터 탐색
    // j를 1부터 K까지 증가시키면서 현재 위치(i)에서 오른쪽으로 j칸 떨어진 곳을 확인
    if (!isEaten) {
      for (let j = 1; j <= K; j++) {
        // 1부터 K까지 증가!
        const rightIdx = i + j; // 오른쪽 햄버거의 인덱스
        if (rightIdx >= N) continue; // 범위 밖이면 건너뜀

        // 햄버거('H')이고 아직 집히지 않았다면
        if (table[rightIdx] === "H" && taken[rightIdx] === false) {
          taken[rightIdx] = true; // 햄버거를 집음
          ans++; // 카운트 증가
          break; // 햄버거를 찾았으니 더 이상 탐색할 필요 없음
        }
      }
    }
  }
}
console.log(ans);

결론과 교훈

  1. 그리디는 항상 가까운 것이 정답이 아니다. -> 나 아니면 못 먹을 가능성이 높은 자원을 확보하는 것이 정답일 수 있다.
  2. “햄버거를 생존시키자” : 뒤늦게 발견되어 버려질 수 있는 멀리있는 햄버거를 선제적으로 선택하여, 살려간다. => 타인에게 영향을 덜 미치며, 동시에 햄버거 소비량을 증가시킴
  3. 이런저런 가설로 “틀렸습니다” 를 두려워 하지 말자.

햄버거… 햄버거 그만.