1. 문제

1) 문제 링크

    : https://programmers.co.kr/learn/courses/30/lessons/81302

 

코딩테스트 연습 - 거리두기 확인하기

[["POOOP", "OXXOX", "OPXPX", "OOXOX", "POXXP"], ["POOPX", "OXPXP", "PXXXO", "OXXXO", "OOOPP"], ["PXOPX", "OXOXP", "OXPOX", "OXXOP", "PXPOX"], ["OOOXX", "XOOOX", "OOOXX", "OXOOX", "OOOOO"], ["PXPXP", "XPXPX", "PXPXP", "XPXPX", "PXPXP"]] [1, 0, 1, 1, 1]

programmers.co.kr

 

2) 설명

면접장(5 by 5)에서 면접자들이 맨해튼거리(가로 길이 + 세로 길이) 기준 2 이상 떨어져 앉아있을 때 거리두기를 지킨 것.

맨해튼거리가 2이하여도 그 사이에 파티션이 있다면 거리두기를 지킨 거다

places(면접장 배열)이 주어지면 각 place(면접장)이 거리두기를 지켰는지(1) 안 지켰는지(0)를 배열로 리턴하는 문제

참고  

- 면접자 : P

- 파티션 : X

- 빈자리 : O

2. 풀이

1) 알고리즘

    : 시뮬레이션(BFS)

 

2) 풀이 설명

- 첫 시도!

BFS를 사용해 풀이를 시도했다.

isFar 함수를 통해 각 place(면접장)이 거리두기가 지켜졌는지 확인한다.

P의 좌표들을 모두 큐에 삽입한다

큐가 빌 때까지 c(거리)가 2 이하이고 해당 P에서 방문한 적이 없는(visited[nx][ny]가 v(탐색 시작한 P의 idx)와 다른) 좌표들의 visited값을 v로 업데이트해준다

다음의 경우 visited[2, 2] 에서 1, 2가 다르기 때문에 그냥 2로 업데이트해주면 되는 거다

    visited 배열    
1 1 1   2
1 P(idx=1) 1 2 2
1 X 1 2 2 P(idx=2)
X X   2 2
P(idx=3) 3 3   2

 

만약 다음과 같이 P를 발견하면 return false 해준다

    visited 배열    
1 1 1   2
1 P(idx=1) 1 2 2
1 X 2 2 P(idx=2)
X X   2 2
        P(idx=3)

 

 

사실 P가 있냐 없냐만 보면 되는데 왜 visited 배열을 썼는가 하면 같은 자리를 두 번 이상 방문하지 않도록 하기 위해서다. 예를 들어 [0, 0]은 P(idx=1)이 좌 -> 상 이동, 상 -> 좌 이동 두 번 방문해야하는데 이때 visited배열을 보고 visited[0][0](=1) 과 v(=1)이 같기 때문에 더 방문하지 않도록 하는 것이다.

 

풀고나서 4개짜리 tuple을 썼다는 게 마음에 안 들었는데 찾아보니 더 간단한 방법이 있었다!

 

- 두 번째 시도!

사실 두 번째 풀이의 핵심은 한 면접자당 BFS를 1 depth까지만 탐색하는 거였다.

i번째 면접자가 방문한 자리를 다른 면접자가 방문한다면 이건 올바르지 않은 거리두기를 하게 된 것이다

설명하자면 교점을 기준으로 보았을 때 [x, y] 교점으로부터 거리가 1인 면접자가 둘 이상 있다면 그 면접자들 간의 거리는 2 이하가 되기 때문이다

예를 들어 다음과 같은 경우에 [4, 3]자리를 P(idx=2)가 방문했는데 P(idx=3)이 또 방문한다면 이 둘은 거리가 2 이하기 때문에 return false 해주면 된다

 

    visited 배열    
  1      
1 P(idx=1) 1   2
  X   2 P(idx=2)
        2 3
        P(idx=3)

 

 

3. 코드

더보기

첫 번째 방법

//BFS 탐색을 2 depth까지 하는 비효율적인 방식의 첫 시도
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <tuple>

using namespace std;

bool isFar(const int boardSize, vector<string> place, vector<vector<int>> &visited){
    const vector<int> dx = {-1, 1, 0, 0};
    const vector<int> dy = {0, 0, -1, 1};
    
    queue<tuple<int, int, int, int>> q;
        
    for(int i=0;i<boardSize;i++){
        for(int j=0;j<boardSize;j++){
            if(place[i][j] == 'P')
				q.push({i, j, 0, q.size()});
        }
    }
	while(!q.empty()){
      	int x = get<0>(q.front());
        int y = get<1>(q.front());
        int c = get<2>(q.front()); // 거리
        int v = get<3>(q.front()); // 탐색 중인 면접자의 index
        q.pop();
        
        visited[x][y] = v;
        
        for(int d=0;d<4;d++){
            int nx = x + dx[d];
            int ny = y + dy[d];
            int nc = c + 1;
                
            if(nx<0 || nx>=boardSize || ny<0 || ny>=boardSize
            	|| visited[nx][ny]==visited[x][y] // 탐색 중인 면접자가 방문한 적이 있다면 continue
               	|| place[nx][ny]=='X' // 파티션이면 continue
              	|| nc>2) // 다음에 갈 자리가 맨해튼 거리 2 초과면 continue
            	continue;
            
            if(place[nx][ny] == 'P')
                return false;
            q.push({nx, ny, nc, v});
        }
    }
    
    return true;
}

vector<int> solution(vector<vector<string>> places) {
	vector<int> answer;
    const int boardSize = 5;
	vector<vector<int>> visited(boardSize, vector<int>(boardSize, -1));
	
    for(auto place : places)
		answer.push_back(isFar(boardSize, place, visited));
    
	return answer;
}

 

두 번째 방법

//1 depth BFS 탐색을 통한 효율적인 방법
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include <tuple>

using namespace std;

bool isFar(const int boardSize, vector<string> place){
    const vector<int> di = {-1, 1, 0, 0};
    const vector<int> dj = {0, 0, -1, 1};
    
    vector<vector<bool>> visited(boardSize, vector<bool>(boardSize, false));
        
    for(int i=0;i<boardSize;i++){
        for(int j=0;j<boardSize;j++){
            if(place[i][j] == 'P'){
                visited[i][j] = true;
            	for(int d=0;d<4;d++){
                    int ni = i + di[d];
            		int nj = j + dj[d];
                    if(ni<0 || ni>=boardSize || nj<0 || nj>=boardSize || place[ni][nj]=='X')
                        continue;
                    if(visited[ni][nj])
                        return false;
                    visited[ni][nj] = true;
                }    
            }
        }
    }
    
    return true;
}

vector<int> solution(vector<vector<string>> places) {
	vector<int> answer;
    const int boardSize = 5;
	
    for(auto place : places)
		answer.push_back(isFar(boardSize, place));
    
	return answer;
}

4. 느낀점

내 생각에는

엄청 길지는 않은 시간에

엄청 느리지는 않은 알고리즘으로

엄청 가독성 떨어지지는 않는 코드로

성공은 했지만 다 최선의 방법은 아니었다ㅠㅠ

면접자가 중심이라 생각해서 면접자를 기준으로만 생각했는데 교점을 기준으로 생각했으면 좋았을 텐데 아쉬웠다...

보이지 않는 걸 보는 연습이 필요한 것 같다..!

'알고리즘 > 프로그래머스' 카테고리의 다른 글

[LV2] 뉴스 클러스터링  (0) 2021.08.26
[LV4] 4단 고음  (0) 2021.08.19
[LV3] 불량 사용자  (0) 2021.08.13
[LV4] 호텔 방 배정  (0) 2021.08.13

1. 문제

1) 문제 링크

    : https://www.acmicpc.net/problem/1034

 

1034번: 램프

첫째 줄에 N과 M이 주어진다. N은 행의 개수이고, M은 열의 개수이다. N과 M은 50보다 작거나 같은 자연수이다. 둘째 줄부터 N개의 줄에는 램프의 상태가 주어진다. 1이 켜져있는 상태이고, 0이 꺼져

www.acmicpc.net

 

2) 설명

램프의 열을 조작하는 스위치를 K번 조작해서 램프가 모두 켜져 있는 행의 개수를 최대로 만드는 문제

2. 풀이

1) 알고리즘

    : 브루트포스

 

2) 풀이 설명

   2-1) 재귀

    첫 시도는 재귀로 시도했으나 시간초과로 실패했다. 생각해보니 열이 최대 50개인데 50개의 열을 켜고 끄는 경우를 생각하면 2의 50제곱이 되어서 당연히 시간초과가 날 수밖에 없던 것 같다.

    그래서 해당 열까지 모든 램프가 켜진 행의 개수가 이전에 구했던 모든 램프가 켜진 행의 최대 개수보다 작거나 같다면 더 탐색을 진행하지 않는다. (왜냐하면 더 탐색을 해봤자 이전에 구한 최대값을 넘을 수 없기 때문에)

    이렇게 하니까 일단 답은 맞았다.

 

  2-2) 맵 사용

    다른 사람들의 풀이를 참고해보니 초기 램프 배열에서 패턴이 똑같은 행의 개수를 구하는 게 더 편해보였다.

    대신 이때 초기에 한 행에서 꺼져 있는 램프의 개수가 K개 이하여야 하고, 남은 스위치 조작 횟수(K - 꺼진 램프 개수)가 짝수 개여야 한다. 스위치를 조작해야될 횟수가 x번 남았을 때 x가 짝수라면 아무 열이나 껐다 켰다 두 번씩 반복하면 되는데, x가 홀수 번이라면 껐다 켰다를 반복하고나서 마지막에 결국 한 번 더 뒤집어야 하기 때문이다.(결국 램프가 다 켜진 행에서 한 번 더 스위치를 바꾸므로 한 개의 램프가 꺼지게 된다)

 

    그래서 맵(key : 램프 행, value : 개수)을 사용해서 초기 램프 행들 중 같은 패턴을 가지는 행 개수를 카운팅 해줬다.

 

    예를 들어

 

    1 - 1001

    2 - 0100

    3 - 1100

    4 - 1001

 

    이렇게 있다면 맵에는 {1001 : 2, 0100 : 1, 1100 : 1} 이렇게 들어가게 된다. 그리고 이 맵을 돌면서 행에서 꺼진 램프의 개수가 K개 이하이고, 꺼진 램프를 모두 켰을 때 (K - 꺼진 램프의 개수)가 짝수인 경우에 답을 업데이트해나갔다.

 

3. 코드

더보기
//재귀 풀이

#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

using namespace std;

int N, M, K, answer = -1;
vector<string> lamp(50);

void reverseCol(int colIdx){
	//colIdx 열 램프 뒤집기
	for(int i=0;i<N;i++)
		lamp[i][colIdx] = (lamp[i][colIdx] == '0'? '1' : '0');
}

int countRow(int colIdx){
	//colIdx까지 몇 개의 램프가 켜져있는지 확인
	if(colIdx == 0)
		return 0;
	int i, j, answer = 0;
	for(i=0;i<N;i++){
		for(j=0;j<colIdx;j++){
			if(lamp[i][j] == '0')
				break;
		}
		if(j == colIdx)
			answer++;
	}
	return answer;
}

/*
	재귀 접근
	@colIdx 열 번호
	@changeCount 램프 열을 켜고 끌 수 있는 남은 횟수
*/
void solve(int colIdx, int changeCount){
	if(colIdx == M){
		//마지막 열일 경우
		if((K - changeCount)%2 == 0)
			//램프를 켜고 끌 수 있는 남은 횟수가 짝수일 경우에만 업데이트
			answer = max(answer, countRow(M));
		return;
	}
	
	//앞서 구한 최대로 켤 수 있는 램프 행의 수보다 켜져있는 램프의 수가 적으면 더 돌리지 않음
	if(countRow(colIdx + 1) > answer)
		solve(colIdx + 1, changeCount);

	reverseCol(colIdx);//뒤집기
	if(countRow(colIdx + 1) > answer && changeCount < K)
		//앞서 구한 최대로 켤 수 있는 램프 행의 수보다 켜져있는 램프 행의 수가 적으면 더 돌리지 않음
		solve(colIdx + 1, changeCount + 1);
	reverseCol(colIdx);//다시 뒤집기
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	cin >> N >> M;
	for(int i=0;i<N;i++)
		cin >> lamp[i];
	cin >> K;
	
	solve(0, 0);

	cout << answer << endl;

	return 0;
}
//패턴과 맵을 이용한 풀이
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <map>

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int N, M, K, answer = 0;
	cin >> N >> M;
	string lamp;
	//램프 행, 개수
	map<string, int> lampMap;

	for(int i=0;i<N;i++){
		cin >> lamp;
		//램프 맵에 카운트 증가
		lampMap[lamp] += 1;
	}
	cin >> K;

	for(auto x : lampMap){
		string row = x.first;
		int val	= x.second;
		//행에 있는 0의 개수
		int zeroCnt = count(row.begin(), row.end(), '0');
		
		//행에 있는 0을 모두 켰을 때 램프 열을 뒤집을 남은 횟수가 짝수여야 함.
		if(zeroCnt <= K && (K - zeroCnt)%2 == 0)
			answer = max(answer, val);
	}

	cout << answer << endl;

	return 0;
}

 

4. 느낀점

- 재귀에서 시간초과난다고 포기하지 않은 나 자신 칭찬한다

- 하지만 아집을 버리고 포기했더라면 더 좋은 방법을 찾을 수도 있었을 텐데 미한한 나 자신 혼낸다

- 사실 스트링 패턴으로 접근하는 풀이는 나 혼자서는 오래 고민한다 해도 생각해내기 힘들었을 것 같다. 좀 더 열린, 다른 시각으로 접근하는 연습을 해보도록 하자

'알고리즘 > 백준' 카테고리의 다른 글

[백준/1727] 커플 맞추기  (0) 2021.08.25
[백준/2110] 공유기 설치  (0) 2021.08.01
[BOJ/7579] 앱  (0) 2020.12.29
[BOJ/14891] 톱니바퀴  (0) 2020.12.18
[백준/9656] 돌 게임 2  (0) 2020.11.02

1. 문제

1) 문제 링크

    : www.acmicpc.net/problem/7579

 

7579번: 앱

입력은 3줄로 이루어져 있다. 첫 줄에는 정수 N과 M이 공백문자로 구분되어 주어지며, 둘째 줄과 셋째 줄에는 각각 N개의 정수가 공백문자로 구분되어 주어진다. 둘째 줄의 N개의 정수는 현재 활

www.acmicpc.net

 

2) 설명

 각각의 프로세스가 메모리를 m_i만큼 사용하고 있고, 각 프로세스를 비활성화시킬 때 드는 비용이 c_i라면, 최소 M만큼의 메모리 공간을 확보하는 데 필요한 최소 비용을 구하는 문제

2. 풀이

1) 알고리즘

    : DP(배낭)

 

2) 풀이 설명

 A) 첫 시도 : 2차원 배열 사용하기

    - 처음에는 메모리 생각을 안 하고 100(N 최댓값) x 10000000(M 최댓값) 2차원 배열을 사용했다.

     메모리 초과가 나서 안 되니까 map을 사용해보자는 생각으로 map을 사용했지만 역시나 메모리 초과로 실패했다.     (어차피 사용량 많아지면 비슷해지는 것 같다)

 

 B) 두번째 시도 : 1차원 배열 사용하기

     - 한참 고민하다가 결국 다른 사람들 풀이를 참고해서 풀게 되었다.

      dp 배열 : 1차원 배열로 dp[cost] : cost를 사용해서 최대로 확보할 수 있는 memory

     이제 이중 반복문을 통해 dp 배열을 업데이트해나갔다.

     점화식 : dp[j] = max(dp[j], dp[j - C[i]] + A[i])

      i번째 프로세스를 비활성화할 경우 dp 배열을 업데이트 해나가는 점화식이다.

      만약 활성화된 채로 두는 게 더 큰 memory를 확보할 수 있다면 dp[j] 가 선택 될 것이고, 비활성화하는 게 더 큰 메모리를 확보할 수 있다면 dp[j-C[i]] + A[i] 가 선택될 것이다.

 

3. 코드

더보기
/*
	앱
	DP
    1차원 배열을 사용한 풀이
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>

#define N_MAX 100
#define C_MAX 100
#define M_MAX 10000000

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int N, M, maxCost, answer;
	vector<int> A(N_MAX, 0), C(N_MAX, 0);
	vector<int> dp(N_MAX*C_MAX + 1, 0);

	cin >> N >> M;
	
	for(int i=0;i<N;i++) cin >> A[i];
	for(int i=0;i<N;i++) cin >> C[i];
	
	maxCost = accumulate(C.begin(), C.end(), 0);
	
	for(int i=0;i<N;i++)
		for(int j=maxCost;j>=C[i];j--)
			dp[j] = max(dp[j], dp[j - C[i]] + A[i]);

	for(answer=0;answer<maxCost && dp[answer]<M;answer++);
	cout << answer << "\n";
}

 

/*
	앱
	DP
    vector와 map을 중첩하여 사용한 풀이
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <map>

#define N_MAX 100
#define C_MAX 100
#define M_MAX 10000000

using namespace std;

int N, M;
vector<int> A(N_MAX, 0), C(N_MAX, 0);
vector<map<int, int>> dp(N_MAX);

int solve(int idx, int needMemory){
	if(idx >= N)
		return M_MAX;	
	if(needMemory <= 0)
		return 0;

	if(dp[idx][needMemory] <= 0)
		dp[idx][needMemory] = min(solve(idx + 1, needMemory), C[idx] + solve(idx + 1, needMemory - A[idx]));

	return dp[idx][needMemory];
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	cin >> N >> M;
	
	for(int i=0;i<N;i++) cin >> A[i];
	for(int i=0;i<N;i++) cin >> C[i];

	cout << solve(0, M) << endl;
}

 

4. 느낀점

1) 좀 더 다방면의 방법을 생각해야겠다. 너무 N, M 값에만 집중해서 생각하다보니 다른 방법을 잘 못 찾았다.

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2110] 공유기 설치  (0) 2021.08.01
[BOJ/1034] 램  (0) 2021.07.27
[BOJ/14891] 톱니바퀴  (0) 2020.12.18
[백준/9656] 돌 게임 2  (0) 2020.11.02
[백준/2631] 줄세우기  (0) 2020.11.02

1. 문제

1) 문제 링크

    : www.acmicpc.net/problem/14891

 

14891번: 톱니바퀴

첫째 줄에 1번 톱니바퀴의 상태, 둘째 줄에 2번 톱니바퀴의 상태, 셋째 줄에 3번 톱니바퀴의 상태, 넷째 줄에 4번 톱니바퀴의 상태가 주어진다. 상태는 8개의 정수로 이루어져 있고, 12시방향부터

www.acmicpc.net

 

2) 설명

삼성 기출인 것 같다. 시뮬레이션 문제..

톱니바퀴 4개를 두고 i번째 톱니를 시계방향 혹은 반시계방향으로 돌리는 문제다.

이때 1, 2, 3, 4 톱니들이 서로 맞물려 있는 바퀴의 값이 다를 때만 옆의 톱니바퀴를 반대방향으로 돌린다.

예를 들어 2번 톱니를 돌리는데 돌리기 전 1번, 2번 톱니가 맞물린 쪽이 S, N으로 다르다면 1번도 돌리고 S, S로 같다면 돌리지 않는다.

 

다 돌리고 나서 각 톱니의 12시 방향에 있는 값을 구하면 된다.

2. 풀이

1) 알고리즘

    : 시뮬레이션

 

2) 풀이 설명

톱니를 돌리는 걸 처음에 직접 구현하려다가 그냥 rotate 함수를 썼다.

사실 시뮬레이션 문제를 잘 안 풀어봐서 처음 써보다보니 음의 방향으로 돌리는 걸 v.begin()기준으로 해서 헷갈렸다.(v.end() 기준으로 해서 조금 더 간단하게 할 수 있었다)

어떤 톱니까지 돌릴지는 rotate 전에 diff라는 vector를 사용해서 미리 구했다.

그리고 diff에 체크된 배열들을 gear_idx 톱니(입력으로 돌리는 톱니)를 기준으로 왼쪽, 오른쪽으로 돌려나갔다.

 

3. 코드

더보기

 

#include <iostream>
#include <vector>
#include <algorithm>

#define N_GEAR 4
#define N_WHEEL 8

using namespace std;


int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	int K, gear_idx, direction, answer = 0;
	vector<vector<bool>> gear(N_GEAR+1, vector<bool>(N_WHEEL, false));
	string input;
	for(int i=1;i<=N_GEAR;i++){
		cin >> input;
		for(int j=0;j<N_WHEEL;j++){
			gear[i][j] = input[j]=='0'? 0 : 1;
		}
	}

	cin >> K;
	vector<bool> diff(5, false);

	while(K--){
		cin >> gear_idx >> direction;
		
		fill(diff.begin(), diff.end(), false);
	
		int left_direction = -direction;
		int right_direction = -direction;

		for(int i=gear_idx+1;i<=N_GEAR;i++)
			diff[i] = gear[i][6] != gear[i-1][2];

		for(int i=gear_idx-1;i>0;i--)
			diff[i] = gear[i][2] != gear[i+1][6];
	
		rotate(gear[gear_idx].begin(), (direction==1? gear[gear_idx].end() - 1 : gear[gear_idx].begin() + 1), gear[gear_idx].end());

		for(int i=gear_idx+1;i<=N_GEAR && diff[i];i++, left_direction *= -1)
			rotate(gear[i].begin(), (left_direction==1? gear[i].end() - 1 : gear[i].begin() + 1), gear[i].end());

		for(int i=gear_idx-1;i>0 && diff[i];i--, right_direction *= -1)
			rotate(gear[i].begin(), (right_direction==1? gear[i].end() - 1 : gear[i].begin() + 1), gear[i].end());
	}
	vector<int> squared = {0, 1, 2, 4, 8};
	for(int i=1;i<=N_GEAR;i++){
		if(gear[i][0])
			answer += squared[i];
	}
	cout << answer << "\n";


	return 0;
}

 

4. 느낀점

 - 시뮬레이션은 단순한 개념을 깔끔하고 효율적으로 구현해내야 하는 게 중요한 것 같다

 - 단순해보인다고 냅다 구현하려다 보면 코드 더러워지면서 혼난다.. 내 코드다 많이 더러워서 나중에 다시 손봐야 할 것 같다..

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/1034] 램  (0) 2021.07.27
[BOJ/7579] 앱  (0) 2020.12.29
[백준/9656] 돌 게임 2  (0) 2020.11.02
[백준/2631] 줄세우기  (0) 2020.11.02
[백준/9252] LCS 2  (0) 2020.10.27

1. 문제

1) 문제 링크

    : www.acmicpc.net/problem/9656

 

9656번: 돌 게임 2

상근이가 게임을 이기면 SK를, 창영이가 게임을 이기면 CY을 출력한다.

www.acmicpc.net

 

2) 설명

돌 게임(9655)이랑 다른 게 없는 문제같다.. 마지막 돌을 가져간 사람이 이기네 지네 차이일 뿐...

2. 풀이

1) 알고리즘

    : DP, 수학

 

2) 풀이 설명

돌 게임 때는 알고리즘이 DP로 분류돼 있어서 DP로 풀었는데 생각해보니까 DP를 쓸 필요가 없는 것 같다..

문제에서 분류해준 알고리즘에 갇혀서 효율적인 풀이를 생각하지 못 했다..

그냥 N이 홀수인지 짝수인지만 판별하면 됐다.. 왜냐하면 N-2번째 돌을 가져가는 사람이 N번째 돌을 가져갈 수밖에 없기 때문이다.(상대방이 N-1번째 돌을 하나 가져가면 N번째 돌을 가져가야 하기 때문) 그럼 결국 N이 홀수일 때는 무조건 상근이가, 짝수일 때는 창영이가 마지막 돌을 가져갈 수밖에 없어진다.

 

3. 코드

더보기
/*
	돌게임2
	DP
*/
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int N;
	cin >> N;

	cout << (N%2? "CY\n" : "SK\n");
	
	return 0;
}

 

 

4. 느낀점

1. 고정관념에 갇히지 말자!

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/7579] 앱  (0) 2020.12.29
[BOJ/14891] 톱니바퀴  (0) 2020.12.18
[백준/2631] 줄세우기  (0) 2020.11.02
[백준/9252] LCS 2  (0) 2020.10.27
[백준/11501] 주식  (0) 2020.10.18

1. 문제

1) 문제 링크

    : www.acmicpc.net/problem/2631

 

2631번: 줄세우기

KOI 어린이집에는 N명의 아이들이 있다. 오늘은 소풍을 가는 날이다. 선생님은 1번부터 N번까지 번호가 적혀있는 번호표를 아이들의 가슴에 붙여주었다. 선생님은 아이들을 효과적으로 보호하기

www.acmicpc.net

 

2) 설명

문제 자체는 이해하기 쉽다. 간단하게 말하면 N명의 아이들이 무작위로 서있는데 얘네를 순서에 맞게 줄세우게 만드는 최소 이동 횟수를 구하면 된다. 예를 들어 1 4 2 3 6 7 5 이렇게 서있으면 1 2 3 4 5 6 7로 세우면 된다. 하지만 나는 푸는 데 오래 걸렸다..

2. 풀이

1) 알고리즘

    : DP

 

2) 풀이 설명

LCS를 사용하면 풀이는 간단하다. 왜냐하면 가장 많이 정렬되어 있는 집단을 고르고 나머지만 옮기는 게 가장 적게 아이들을 이동시키며 줄세울 수 있는 방법이기 때문이다.

 

위에 예에서 1 2 3 6 7 이 LCS가 된다. 그러면 4, 5만 제자리 찾아서 옮겨주면 되니까 최소 이동 횟수는 2가 된다.

 

사실 LCS는 얼마 전에도 풀고 여러번 했었는데, 머리가 나빠선지 응용 문제가 나오니까 LCS가 생각도 안 났다.

한 이틀 정도 헛다리만 집었지만 다행이도 스스로 LCS를 생각해서 풀 수 있었다.

솔직히 LCS를 풀어본 경험이 있다면 굉장히 쉬운 문제라고 생각하지만 나는 이틀을 고민해서 찾아낸 만큼 기분이 아주 좋았다.

 

3. 코드

더보기

 

/*
	줄 세우기
	DP
*/
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);

	int N;
	cin >> N;

	vector<int> children(N, 0), dp(N, 1);
	
	for(int i=0;i<N;i++)
		cin >> children[i];
	
	for(int i=0;i<N;i++)
		for(int j=i-1;j>=0;j--)
			if(children[i] > children[j])
				dp[i] = max(dp[i], dp[j] + 1);

	cout << N - *max_element(dp.begin(), dp.end()) << "\n";

	return 0;
}

 

4. 느낀점

1. 응용 문제를 자주 풀어보자!

'알고리즘 > 백준' 카테고리의 다른 글

[BOJ/14891] 톱니바퀴  (0) 2020.12.18
[백준/9656] 돌 게임 2  (0) 2020.11.02
[백준/9252] LCS 2  (0) 2020.10.27
[백준/11501] 주식  (0) 2020.10.18
[백준/15312] 이름 궁합  (1) 2020.10.18

1. 문제

1) 문제 링크

    : www.acmicpc.net/problem/9252

 

9252번: LCS 2

LCS(Longest Common Subsequence, 최장 공통 부분 수열)문제는 두 수열이 주어졌을 때, 모두의 부분 수열이 되는 수열 중 가장 긴 것을 찾는 문제이다. 예를 들어, ACAYKP와 CAPCAK의 LCS는 ACAK가 된다.

www.acmicpc.net

 

2) 설명

 

2. 풀이

1) 알고리즘

    : DP

 

2) 풀이 설명

두 문자열이 주어질 때 가장 긴 공통 문자열의 길이와 그 문자열을 추출하는 문제.
ex) ABCDEG, BDAEFG 라면 BDEG가 가장 긴 공통 문자열이 되고 길이는 4가 된다.

첫 접근은 DP를 사용해서 공통 문자열의 길이를 구했다.
예를 들어 문자열 A, B가 주어지면 solve(A_idx, B_idx)를 재귀적으로 사용해서 가장 긴 문자열의 길이를 구하는 방식이다.
dp[x][y] 에는 A의 x번째 문자, B의 y번째 문자로부터 만들 수 있는 가장 긴 공통 문자열의 길이를 반환한다.
그러나 이 방식으로는 문자열의 길이는 구해도 어떤 문자열인지 추적하는 일이 너무 힘들었다.
그래서 검색 결과 다른 방식으로 DP를 사용하는 법을 배웠다.

일단 탑다운 방식보다는 바텀업 방식으로 진행해나갔다.

dp[i][j] 는 A의 i번째 문자까지와 B의 j-1번째 문자까지, B의 j번째 문자까지와 A의 i-1번째 문자까지 비교했을 때 가장 긴 공통 문자열의 길이를 의미한다.

그러므로 A[i-1] == B[j-1] 이라면 dp[i][j] = dp[i-1][j-1] + 1이 되고, 같지 않다면 dp[i][j] = max(dp[i-1][j], dp[i][j-1])이 된다.

이부분이 상당히 이해하기 까다로웠다.

내가 이해한 바로는 (A[i], B[j-1])로 만들 수 있는 가장 긴 공통 문자열의 길이, (A[i-1], B[j])로 만들 수 있는 가장 긴 공통 문자열의 길이는 (A[i-1], B[j-1])로 만들 수 있는 가장 긴 공통 문자열의 길이와 같거나 1개 더 크기 때문으로 이해했다.

이렇게 다 돌고나면 dp[A의 길이][B의 길이]가 가장 긴 공통 문자열의 길이가 된다.

 

이후 만들어놓은 자취를 따라서 역추적해서 정답 문자열을 추출해내면 된다.

3. 코드

더보기
/*
	LCS 2
	DP
	Top-down
    길이만 구할 수 있었다..
*/
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#define MAX 1000

using namespace std;

string A = "", B = ""; 
vector<vector<int>> dp(MAX, vector<int>(MAX, -1));

int solve(int A_idx, int B_idx){
	if(A_idx == A.length() || B_idx == B.length())
		return 0;
	if(dp[A_idx][B_idx]>=0)
		return dp[A_idx][B_idx];
	for(int i=A_idx;i<A.length();i++){
		for(int j=B_idx;j<B.length();j++){
			if(A[i] == B[j]){
				dp[A_idx][B_idx] = max(dp[A_idx][B_idx], solve(i+1, j+1)+1);
			}
		}
	}
	return dp[A_idx][B_idx];
}

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	cin >> A >> B;
	
	cout << solve(0, 0) << "\n";

	return 0;
}
/*
	LCS 2
	DP
	Bottom-up
*/
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#define MAX 1000

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	string A = "", B = ""; 
	cin >> A >> B;
	
	vector<vector<int>> dp(MAX, vector<int>(MAX, 0));

	//길이 구하기
	for(int i=1;i<=A.length();i++){
		for(int j=1;j<=B.length();j++){
			if(A[i-1] == B[j-1]){
				dp[i][j] = dp[i-1][j-1] + 1;
			} else {
				dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
			}
		}
	}
	cout << dp[A.length()][B.length()] << endl;
	
	//경로 찾아가기
	string answer = "";
	
	int x = A.length();
	int y = B.length();
	
	while(dp[x][y]>0){
		if(dp[x][y] == dp[x-1][y]){
			x--;
		} else if(dp[x][y] == dp[x][y-1]){
			y--;
		} else {
			answer = A[x-1] + answer;
			x--;
			y--;
		}
	}
	cout << answer << endl;
	return 0;
}

 

4. 느낀점

 - 머리로는 대강 알겠는데 설명하는 게 너무 어렵다. 대강 이해한 게 문제인 것 같다.. 내 생각을 남들이 이해할 수 있도록 표현하는 연습을 하는 것도 중요하니까 어렵더라도 계속 설명하려 노력해야겠다.

 - 단순히 길이만을 구하는 탑다운 풀이에서 벗어나오지를 못했다... 안 될 것 같으면 고집부리지 말고 다른 방식의 풀이도 생각해보자.

'알고리즘 > 백준' 카테고리의 다른 글

[백준/9656] 돌 게임 2  (0) 2020.11.02
[백준/2631] 줄세우기  (0) 2020.11.02
[백준/11501] 주식  (0) 2020.10.18
[백준/15312] 이름 궁합  (1) 2020.10.18
[백준/17070] 파이프 옮기기 1  (0) 2020.10.18

1. 문제

1) 문제 링크

    : www.acmicpc.net/problem/11501

 

11501번: 주식

입력의 첫 줄에는 테스트케이스 수를 나타내는 자연수 T가 주어진다. 각 테스트케이스 별로 첫 줄에는 날의 수를 나타내는 자연수 N(2 ≤ N ≤ 1,000,000)이 주어지고, 둘째 줄에는 날 별 주가를 나타

www.acmicpc.net

 

2) 설명

일별 주가가 주어졌을 때 주식을 사고 팔아 얻을 수 있는 최대의 이윤을 구하는 문제.

하루에 주식 하나 이하로만 살 수 있다.

2. 풀이

1) 알고리즘

    : 단순 구현

 

2) 풀이 설명

 주식을 매수한 시점보다 미래에 더 높은 가격에 매도할 수만 있으면 그 시점에는 무조건 주식을 매수해야 한다.(파는 건 여러 개 매도할 수 있기 때문)

그래서 해당 시점(i)에 주식을 매수했을 때 가능한 가장 높은 매도가를 기록해놓는 sellPrice 벡터를 사용했다.

그리고 sellPrice[i] - stock[i](매도가 - 매수가)가 양수일 때만 answer (답)에 더해주었다.

 

3. 코드

더보기

 

/*
	주식
	단순 구현
*/
#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	
	int T;

	cin >> T;

	while(T--){
		long long N, answer = 0;
		cin >> N;
		vector<long long> stock(N);
		vector<long long> sellPrice(N, 0);

		for(int i=0;i<N;i++)
			cin >> stock[i];
		
		for(int i=N-2;i>=0;i--)
			sellPrice[i] = max(sellPrice[i+1], stock[i+1]);

		for(int i=0;i<N;i++)
			answer += max((sellPrice[i] - stock[i]), (long long)0);
		cout << answer << endl;
	}

	return 0;
}

 

4. 느낀점

- 처음에 별 생각 없이 sellPrice를 O(n^2)으로 구했다가 시간초과 났다.. 시간 많이 준다고 해도 (5초) 효율성 고려하자..

'알고리즘 > 백준' 카테고리의 다른 글

[백준/2631] 줄세우기  (0) 2020.11.02
[백준/9252] LCS 2  (0) 2020.10.27
[백준/15312] 이름 궁합  (1) 2020.10.18
[백준/17070] 파이프 옮기기 1  (0) 2020.10.18
[백준/5557] 1학년  (1) 2020.10.18

+ Recent posts