https://www.acmicpc.net/problem/14722
14722번: 우유 도시
영학이는 딸기우유, 초코우유, 바나나우유를 좋아한다. 입맛이 매우 까다로운 영학이는 자신만의 우유를 마시는 규칙이 있다. 맨 처음에는 딸기우유를 한 팩 마신다. 딸기우유를 한 팩 마신 후
www.acmicpc.net
문제
영학이는 딸기우유, 초코우유, 바나나우유를 좋아한다.
입맛이 매우 까다로운 영학이는 자신만의 우유를 마시는 규칙이 있다.
- 맨 처음에는 딸기우유를 한 팩 마신다.
- 딸기우유를 한 팩 마신 후에는 초코우유를 한 팩 마신다.
- 초코우유를 한 팩 마신 후에는 바나나우유를 한 팩 마신다.
- 바나나우유를 한 팩 마신 후에는 딸기우유를 한 팩 마신다.
저번 축제에서 수많은 우유를 마셨지만 더욱 우유에 갈증을 느낀 영학이는 우유 여행을 떠났다.
맛있는 우유를 찾아 떠난 영학이는 수많은 우유 가게로 둘러 쌓인 어느 도시에 도착했다.
이 도시는 정사각형 형태의 2차원 격자 모양으로 남북으로 N개, 동서로 N개, 총 N*N개의 우유 가게들이 있다.
영학이는 도시의 서북쪽 끝 (1, 1)에서 출발해서 동남쪽 아래 (N, N)까지 까지 가면서 우유를 사 마신다.
각각의 우유 가게는 딸기, 초코, 바나나 중 한 종류의 우유만을 취급한다.
각각의 우유 가게 앞에서, 영학이는 우유를 사 마시거나, 사 마시지 않는다.
So cooooool~한 영학이는 오직 동쪽 또는 남쪽으로만 움직이기 때문에 한 번 지나친 우유 가게에는 다시 가지 않는다.
영학이가 마실 수 있는 우유의 최대 개수를 구하여라.
입력
첫 번째 줄에는 우유 도시의 영역 크기 N이 주어진다. (1 ≤ N ≤ 1000)
두 번째 줄부터 N+1 번째 줄까지 우유 도시의 정보가 주어진다.
0은 딸기우유만을 파는 가게, 1은 초코우유만을 파는 가게, 2는 바나나우유만을 파는 가게를 뜻하며, 0, 1, 2 외의 정수는 주어지지 않는다.
출력
영학이가 마실 수 있는 우유의 최대 개수를 출력하시오.
접근방법 및 풀이
처음에는 다음과 같은 BFS 방식으로 접근했다.
#include <iostream>
#include <queue>
#include <algorithm>
#define VMAX 1000
using namespace std;
int n;
int b[VMAX][VMAX];
int result=0;
int w[VMAX][VMAX]={0}; // mem 배열
int dx[2] = {0,1};
int dy[2] = {1,0};
void bfs(){
queue<pair<pair<int,int>,pair<int, int>>> q;
if(b[0][0]==0) w[0][0]=1;
q.push({{0,0},{w[0][0], w[0][0]}});
while(!q.empty()){
int x = q.front().first.first;
int y = q.front().first.second;
int c = q.front().second.first;
int next = q.front().second.second;
q.pop();
if(x==n-1 && y==n-1){
result = max(result, c);
}
for(int i=0; i<2; i++){
int nx = x + dx[i];
int ny = y + dy[i];
if(nx < n && ny < n && w[nx][ny] < c){
if(next == b[nx][ny]){
next++;
c++;
if(next==3) next=0;
}
w[nx][ny] = c;
q.push({{nx,ny}, {c, next}});
}
}
}
return ;
}
int main()
{
cin >> n;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
scanf("%d", &b[i][j]);
}
}
bfs();
cout << result;
return 0;
}
바로 시간초과가 났다.
n의 최대값이 1000이라 당연한 결과였다.
바로 DP로 접근해야겠다는 판단이 들었다.
먼저 현재칸의 우유를 마실 수 있는지 여부를 판단하여 위쪽에서 내려오는 것과 왼쪽에서 오는 것 중 어느쪽이 이득인지 확인한다.
그 후 이득이 방향으로 우유사실 수 있는지 여부를 통해 mem을 채워나간다.
#include <iostream>
#include <queue>
#include <algorithm>
#include <vector>
#define VMAX 1000
#define INF 987654321
using namespace std;
int findnext(int next){
next++;
if(next==3) next=0;
return next;
}
int main(){
int n;
int b[VMAX][VMAX];
int mem[VMAX][VMAX];
int v[VMAX][VMAX]={0};
cin >> n;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
scanf("%d", &b[i][j]);
}
}
if(b[0][0]==0){
mem[0][0]=1;
v[0][0]=1;
}else{
mem[0][0]=0;
v[0][0]=0;
}
for(int i=1; i<n; i++){
int next = v[0][i-1]; // 이번차례에 마실 수 있는 우유 종류 구하기
if(next==b[0][i]){
mem[0][i] = mem[0][i-1]+1;
v[0][i]=findnext(next);
}else{
mem[0][i] = mem[0][i-1];
v[0][i] = next;
}
}
for(int i=1; i<n; i++){
int next = v[i-1][0];// 이번차례에 마실 수 있는 우유 종류 구하기
if(next==b[i][0]){
mem[i][0] = mem[i-1][0]+1;
v[i][0]=findnext(next);
}else{
mem[i][0] = mem[i-1][0];
v[i][0]=next;
}
}
for(int i=1; i<n; i++){
for(int j=1; j<n; j++){
// 이번차례에 마실 수 있는 우유 종류 구하기 - 각 위,왼쪽에 대해서
int next_top = v[i-1][j];
int next_left = v[i][j-1];
int top, left;
if(b[i][j] == next_top){
top = mem[i-1][j] + 1;
}else{
top = mem[i-1][j];
}
if(b[i][j] == next_left){
left = mem[i][j-1] + 1;
}else{
left = mem[i][j-1];
}
if(top > left){ // top 기준으로 구하기
mem[i][j] = top;
if(next_top==b[i][j]){
v[i][j]=findnext(next_top);
}else{
v[i][j]=next_top;
}
}else{ // left 기준으로 구하기 어차피 우유 number를 고려한 상태이기 때문에 상관없다.
mem[i][j] = left;
if(next_left==b[i][j]){
v[i][j]=findnext(next_left);
}else{
v[i][j]=next_left;
}
}
}
}
cout << mem[n-1][n-1];
return 0;
}
느낀점
- DP의 위력을 실감했다.
- 점화식을 좀더 깔끔하게 세워서 mem 배열하나로 접근해 다이렉트로 풀지는 못해서 아쉽다.
'알고리즘 문제풀이' 카테고리의 다른 글
[백준] 1799번 비숍 c++ (0) | 2021.08.28 |
---|---|
[백준] 16509번 장군 c++ (0) | 2021.08.27 |
[백준] 14719번 빗물 c++ (0) | 2021.08.26 |
[백준] 14716번 현수막 c++ (0) | 2021.08.26 |
[백준] 2211번 네트워크 복구 c++ (0) | 2021.08.24 |