백준 4417, 9305 - Yahtzee

최원빈·2022년 10월 17일
0

https://www.acmicpc.net/problem/9305
https://www.acmicpc.net/problem/4417

먼저 9305번, 최적 조합의 해를 구하는 문제다.

13개의 주사위 조합을 13개의 족보(category)에 넣어 최고점을 출력하기만 하면 되는 문제이다.

처음 생각은 dp[족보][주사위 조합][사용된 주사위 조합의 비트마스킹]
dp[13][13][1 << 13] 을 활용해 bitmasking + dp로 풀어내려고 했으나, 메모리도 시간도 조건과 안맞았다.

문제를 더 이해해보면 모든 족보를 전부 한 번씩, 모든 주사위 조합을 사용해야 하는 문제이기때문에

dp[족보][사용된 주사위 조합의 비트마스킹] 만으로도 모든 경우를 계산할 수 있었다.

Ones에 0~12번 조합을 전부 넣어보고 ... dp[0][visited | (1 << i)]

Twos에 비트마스킹으로 방문하지 않은 모든 조합을 전부 넣어보고 ... dp[1][visited | (1 << i)]

dp[12][1 << 13]의 캐시에 모든 정보를 담을 수 있었다.

보너스 계산을 위해 방문하는 족보의 순서를 top-down의 역으로 바꾸었다. (Yahtzee -> FullHouse -> ... )

#include <iostream>
#include <algorithm>
using namespace std;

int c[13][5];
int dp[13][8192];
int Nums(int x[], int n){
  int point = 0;

  for(int i = 0; i < 5; i++){
    if(x[i] == n) point += n;
  }

  return point;
}

int Chance(int x[]){
  return x[0] + x[1] + x[2] + x[3] + x[4];
}

int ToK(int x[]){
  if((x[0] == x[2]) || (x[1] == x[3]) || (x[2] == x[4])){
    return x[0] + x[1] + x[2] + x[3] + x[4];
  }
  return 0;
}

int FoK(int x[]){
  if((x[0] == x[3]) || (x[1] == x[4])){
    return x[0] + x[1] + x[2] + x[3] + x[4];
  }
  return 0;
}

int SS(int x[]){
  int temp[7] = {0, 0, 0, 0, 0, 0, 0};
  for(int i = 0; i < 5; i++){
    temp[x[i]]++;
  }
  if(temp[1] && temp[2] && temp[3] && temp[4]) return 25;
  if(temp[2] && temp[3] && temp[4] && temp[5]) return 25;
  if(temp[3] && temp[4] && temp[5] && temp[6]) return 25;
  return 0;
}

int LS(int x[]){
  for(int i = 0; i < 4; i++){
    if(x[i] != x[i+1] - 1){
      return 0;
    }
  }
  return 35;
}

int FH(int x[]){
  if(x[0] == x[1] && x[2] == x[4]) return 40;
  if(x[0] == x[2] && x[3] == x[4]) return 40;
  return 0;
}

int Yahtzee(int x[]){
  if(x[0] == x[4]){
    return 50;
  }
  else return 0;
}


int getPoint(int x[], int category){
  if(category < 6){
    return Nums(x, category + 1);
  }
  if(category == 6){
    return Chance(x);
  }
  if(category == 7){
    return ToK(x);
  }
  if(category == 8){
    return FoK(x);
  }
  if(category == 9){
    return SS(x);
  }
  if(category == 10){
    return LS(x);
  }
  if(category == 11){
    return FH(x);
  }
  else return Yahtzee(x);
}

int getMax(int category, int visited){
  if(category < 0) return 0;
  int &ret = dp[category][visited];
  if(ret != -1) return ret;
  ret = 0;
  for(int i = 0; i < 13; i++){
    if(visited & (1 << i)) continue;
    ret = max(ret, getMax(category - 1, visited | (1 << i)) + getPoint(c[i], category));
  }
  if(category == 5 && ret >= 63) ret += 35;
  return ret;
}

int main(){
  cin.tie(NULL);
  ios_base::sync_with_stdio(false);
  int T;
  cin >> T;

  for(int t = 0; t < T; t++){
    for(int i = 0; i < 13; i++){
      fill(dp[i], dp[i] + 8192, -1);
    }

    for(int i = 0; i < 13; i++){
      for(int j = 0; j < 5; j++){
        cin >> c[i][j];
      }
    }
    for(int i = 0; i < 13; i++){
      sort(c[i], c[i] + 5);
    }
    int ans = 0;
    cout << "Case " << t + 1  <<": " << getMax(12, 0) << "\n";
  }
}

출력이 "Case i: ans" 형태라는 점에 유의하자. 이거 몰라서 몇시간을..


다음은 4417번, 조합의 최고값과 해당 최고값을 만드는 데 사용된 점수들을 전부 추적해야하는 문제이다.

dp와 같은 크기의 track배열을 만들어서 dp의 지점별 최고값이 갱신될 때마다
track이 해당 비트마스크를 기억하도록 구현하였다.

int getMax(int category, int visited){
  if(category < 0) return 0;
  int &ret = dp[category][visited];
  if(ret != -1) return ret;
  ret = 0;
  for(int i = 0; i < 13; i++){
    if(visited & (1 << i)) continue;
    int ans = getMax(category - 1, visited | (1 << i)) + getPoint(c[i], category);
    if(ret < ans){
      ret = ans;
      track[category][visited] = (visited | (1 << i));
    }
  }
  if(category == 5 && ret >= 63) ret += 35;
  return ret;
}

visited를 기억하면, dp[category][visited]로 그때의 점수에 쉽게 접근할 수 있다.

    //....
    int ans = getMax(12, 0);
    
    int bef = 0;
    stack<int> history;
    history.push(dp[12][0]);
    for(int i = 12; i > 0; i--){
      bef = track[i][bef];
      history.push(dp[i - 1][bef]);
    }

    int s = 0;
    int bonus = 0;
    for(int i = 0; i < 6; i++){
      int temp = history.top();
      bonus += temp - s;
      if(i == 5 && bonus >= 63) cout << temp - s - 35 << " ";
      else cout << temp - s << " ";
      s = temp;
      history.pop();
    }

    for(int i = 0; i < 7; i++){
      int temp = history.top();
      cout << temp - s << " ";
      s = temp;
      history.pop();
    }

    cout << (bonus >= 63 ? 35 : 0) << " ";
    cout << ans << "\n";

아, 이 문제는 Yahtzee의 족보가 마지막이 아니고 Four of King 다음이라는 점도 유의해야 한다.


영차영차 알고리즘도 하는중...ㅎ

profile
FrontEnd Developer

0개의 댓글