동적계획법으로 풀었다.

memset에서 초기화 해줄 때 크기에 자료형 크기만큼 해주지 않아 초기에 오답이 발생했다.

메모이제이션.

base case 초기에 넣어주고 이미 기록 되어있는지 확인. 그 위치에 간 기록이 있고 결과가 있는지.

 

있다면 바로 반환, 없다면 계산을 하기위해 다시 문제를 작은문제로 분할.

아래로 가는경우와 오른쪽으로 가는 경우.

 

https://algospot.com/judge/problem/read/JUMPGAME

 

algospot.com :: JUMPGAME

외발 뛰기 문제 정보 문제 땅따먹기를 하다 질린 재하와 영훈이는 땅따먹기의 변종인 새로운 게임을 하기로 했습니다. 이 게임은 그림과 같이 n*n 크기의 격자에 각 1부터 9 사이의 정수를 쓴 상태로 시작합니다. 각 차례인 사람은 맨 왼쪽 윗 칸에서 시작해 외발로 뛰어서 오른쪽 아래 칸으로 내려가야 합니다. 이 때 각 칸에 적혀 있는 숫자만큼 오른쪽이나 아래 칸으로 움직일 수 있으며, 중간에 게임판 밖으로 벗어나면 안 됩니다. 균형을 잃어서 다른 발로 서거

algospot.com

 

#include <bits/stdc++.h>

using namespace std;

int board[100][100];
int cache[100][100];

int sol(int x, int y, int size){
	if(x>=size || y>=size){
		//넘침 
		return 0;
	}
	if(x == size-1 && y == size -1){
//		도착 
		return 1;
	} 
	
	if(cache[x][y] != -1){
		return cache[x][y];
	} 
	int jum = board[x][y];
	int &ret = cache[x][y];
	//오른쪽으로 가능 경우
	return ret = max(sol(x+jum, y, size), sol(x, y+jum, size));
	//아래로 가는 경우 
}

int main(){
  int n;
  cin>>n;
  for(int _times = 0; _times < n; ++_times)
  {
  	memset(cache, -1, 10000*sizeof(int));
	int _size;
	cin>>_size;
	for(int i=0; i<_size; ++i){
	  for(int j=0; j<_size; ++j){
		cin>>board[i][j];
	  }
	}
	if(sol(0, 0, _size)==1){
		cout<<"YES"<<endl;
	}else{
		cout<<"NO"<<endl;
	}
  }
 return 0;
}

Dev C++ 설치 경로: https://sourceforge.net/projects/orwelldevcpp/

코딩테스트 준비를 위해 C++로 학습하는데, 무거운 비주얼 스튜디오로 하고 싶지 않아 정보를 찾던 중

 

Dev C++을 알게되어 설치하였다.

 

출처:

https://ndb796.tistory.com/165

 

알고리즘 대회를 위한 C++ 대회 환경 구성하기

이번 시간에는 알고리즘 대회를 위한 C++ 대회 환경을 구성해보도록 하겠습니다. 가장 먼저, 자신의 디버깅 및 개발을 위한 개발환경을 구축하셔야 합니다. 저는 정말정말 가벼운 개발환경인 Dev C++을 선호하는..

ndb796.tistory.com

 

'참고 링크 > 개발' 카테고리의 다른 글

플러터 개발  (0) 2020.03.18
[안드로이드] OpenGL 참고용  (0) 2020.03.13
이펙티브 자바  (0) 2020.03.09
리팩토링 관련 사이트  (0) 2020.03.01
[유니티]너랑나랑개발자 이야기  (0) 2019.12.28

7.1 도입

Divide & Conquer은 유명한 알고리즘 패러다임이다.

분할정복 알고리즘은 문제를 부분 문제로 나눈 뒤 각 문제에 대한 답을 재귀 호출을 이용해 계산하고, 부분 문제의 답으로부터 전체 문제의 답을 계산해 낸다.

 

일반적인 재귀호출과 다른 점은 문제를 한조각과 나머지로 나누는 대신, 비슷한 크기의 부분문제로 나누는 것이다.

 

분할정복을 사용하는 알고리즘은 대개 세가지의 구성요소를 갖는다.

  • 문제를 더 작은 문제로 분할하는 과정 (divide)
  • 각 문제에 대해 구한 답을 원래 문제의 답으로 병합하는 과정 (merge)
  • 더이상 답을 분할하지 않고 곧장 풀 수 있는 매우 작은 문제 (base case)

분할정복으로 문제를 풀기 위해서는 문제에 몇가지 특성이 있어야 한다.

문제를 둘 이상의 부분 문제로 나누는 것이 자연스러워야하며, 부분 문제의 답을 조합해 원래 문제의 답을 계산하는 효율적인 방법이 있어야한다.

 

분할정복의 장점은 같은 작업을 더 빠르게 처리하는 것이다.

 

7.2 문제: 쿼드 트리 뒤집기 (문제 ID: QUADTREE, 난이도: 하)

쿼드 트리는 대량의 좌표 데이터를 메모리안에 압축해 저장하기 위해 사용하는 기법이다.

 

주어진 공간을 4개로 분할해 재귀적으로 표현하기에 쿼드 트리라는 이름이 붙었다.

가장 대표적인 사용처는 흑백 이미지를 압축해서 표현하는 것이다.

 

쿼드 트리는 2ⁿ X 2ⁿ 크기의 흑백 이미지를 다음과 같이 문자열로 압축한다.

 

이미지의 모든 픽셀이 검은 색인 경우 쿼드 트리로 변환한 결과는 그림의 크기와 관계없이 b가 된다.

이미지의 모든 픽셀이 흰 색인 경우 쿼드 트리로 변환한 결과는 그림의 크기와 관계없이 w가 된다.

 

모든 픽셀이 같은 색이 아니라면, 쿼드 트리는 이 이미지를 가로 세로로 각각 2등분해 4개의 조각으로 쪼갠 뒤 각각을 쿼드 트리 압축한다. 압축 결과는 x(좌상단 압축결과)(우상단 압축결과)(좌하단 압축결과)(우하단 압축결과)가 된다.

 

쿼드트리로 압축된 흑백 이미지가 주어졌을 때, 이 이미지를 상하로 뒤집은 이미지를 쿼드트리 압축하여 출력하는 프로그램을 작성하시오.

 

시간 및 메모리 제한

프로그램은 1초안에 실행되어야 하며, 64MB이하의 메모리를 사용해야 한다.

 

입력

첫 줄에 테스트 케이스의 개수 C(C≤50)가 주어진다. 그 후 C줄에 하나씩 쿼드 트리로 압축된 텍스트가 주어진다. 문자열의 길이는 1000이하이고, 원본 이미지의 크기는 2^20 X 2^20을 넘지 않는다.

 

출력

상하로 뒤집은 쿼드트리 텍스트를 출력한다.

 

7.3 풀이: 쿼드 트리 뒤집기

문제를 푸는 가장 무식한 방법은 쿼드 트리로 실제 이미지를 얻고 상하반전한 뒤 다시 쿼드트리 압축하는 것이다.

그러나 제한에 걸려 이 방법을 사용할 수 없다.

 

이런경우 선택가능한 방법은 두가지이다.

  • 큰 입력에 대해서도 동작하는 효율적인 알고리즘을 처음부터 새로 만들기
  • 작은 입력에 대해서만 동작하는 단순한 알고리즘으로부터 시작해서 최적화해 나가기

시행착오와 경험이 중요하다.

 

쿼드 트리 압축 풀기

쿼드 트리가 재귀적으로 정의되어 있기 때문에 쿼드 트리를 압축하거나 해제하는 과정은 재귀 호출로 구현하는 것이 자연스럽다.

 

문자열 s의 압축을 해제해서 배열에 저장하는 함수 decompress()를 구현하자.

base case는 s의 첫 글자가 w나 b인 경우이다. 이 경우 배열에 저장 후 종료한다.

첫 글자가 x라면 s의 나머지 부분을 넷으로 쪼개 재귀 호출한다.

이 때 각 부분이 배열의 어느 부분에 저장되어야 하는지 지정하는 위치 오프셋도 전달해야한다.

 

char decompressed[MAX_SIZE][MAX_SIZE];

void decompress(const string& s, int y, int x, int size);

 

압축 문자열 분할하기

구현을 시작하면 s의 나머지 부분을 넷으로 쪼개기가 까다롭다는 사실을 깨닫게 된다.

각 부분의 길이가 일정하지 않기 때문이다.

이를 해결할 수 있는 첫 번째 방법은 주어진 위치에서 시작하는 압축결과의 길이를 반환하는 함수 getChunkLength()를 만드는 것이다.

s[0]이 x라고 하면 왼쪽 위 조각을 나타내는 부분은 항상 s[1]에서부터 시작한다.

이 때 getChunkLength(s, 1)이 해당 부분의 길이를 반환하도록 하는 것이다. 그 후 반환한 값에 이전 위치를 더해서 다음 부분을 구할 수 있다.

이 함수를 작성하는 것은 어렵지 않지만, 비슷한 일을 하는 함수를 나누어 작성하는 것이 마음에 들지 않을 것이다.

 

이럴 때 유용한 패턴은 s를 미리 쪼개는 것이 아니라 decompress()함수에서 '필요한 만큼 가져다 쓰도록' 하는 것 이다.

s를 통째로 전달하는 것이 아니라, s의 한 글자를 가리키는 포인터를 전달한다.

함수 내에서는 한 글자를 검사할 때마다 포인터를 앞으로 옮긴다.

char decompressed[MAX_SIZE][MAX_SIZE]
void decompress(string::iterator& it, int y, iny x, int size){
    //한글자를 검사할 때마다 반복자를 한 칸 앞으로 옮긴다.
    char head = *(it++);
    //base case: 첫 글자가 b또는 w인경우
    if(head == 'b' || head == 'w'){
        for(int dy = 0; dy < size; ++dy)
            for(int dx = 0; dx < size; ++dx)
                decompressed[y+dy][x+dx] = head;
    }
    else{
        //네 부분을 순서대로 압축 해제한다.
        int half = size/2;
        decompress(it, y, x, half);
        decompress(it, y, x+half, half);
        decompress(it, y+half, x, half);
        decompress(it, y+half, x+half, half);
    }
}

압축 다 풀지 않고 뒤집기

string reverse(string::iterator& it){
    char head = *iter;
    ++iter;
    
    if(head == 'b'|| head == 'w')
        return string(1, head);
    string upperLeft = reverse(it);
    string upperRight = reverse(it);
    string lowerLeft = reverse(it);
    string lowerRight = reverse(it);
    //각각 위와 아래 조각들의 위치를 바꾼다.
    return string("x") + lowerLeft + lowerRight + upperLeft + upperRight;
}

 

카라츠바

+ Recent posts