알고리즘 문제 풀기

swexpertacademy - 1770. 블록 부품 맞추기

studying develop 2020. 1. 14. 16:58

두 부품의 기둥 면을 마주하여 조립하면, 완벽히 일치(기둥 사이에 틈새가 존재하지 않는)하는 육면체 완성품을 얻을 수 있다.

이렇게 조립한 육면체의 높이는 8이 된다. 이때 이 육면체 완성품은 재사용될 수 있으므로, 절약되는 비용은 육면체의 높이 8만큼이다.

밑면의 [가로 x 세로] 넓이가 각각 [4 x 4]인 블록 부품이 30,000개 주어질 때, 절약되는 완성품들의 총 합이 최대가 되도록 하자.

 ※ 한개의 완성품은 항상 두개의 블럭으로 구성된다.

▶ 절약되는 완성품들의 총 합의 최대값을 반환하는 makeBlock() 함수를 작성하라.

 

일일이 두개씩 골라서 다 비교하면 4x4 x 30000 x 29999 = 16*9*10^8 

 

절약되는 값이 최대가 되게 하려면 음 기둥 높이가 2,3,5,8 가 있으면  어차피 높이 합은다 똑같으니까 누구랑 결합하느냐가 중요한건 아닌거 같고 그냥 결합할 수 있으면 결합하면 되는건가? 근데 a b c d면

a는 b,c랑 결합되고 c,d가 되면 a,d도 결합되는거 잔아. 왜냐면 a가 홈이 c랑 맞는데 c가 d랑 맞으면 a도 d랑 맞는거지....

 

1. 음 결국에는 일단 결합할 수 있는지 확인하는게 필요하다. 

이 부분은 4번 회전해보고 둘이 결합 가능한지 (더한 높이들이 같은지 확인해보면 된다.)

 

2. 그리고 결합은 그냥하면된다. 근데 최적화하기 위해서 생각해야 하는것이 생각났다.

 

3. abcdef 있을때

a가 bdf랑 결합 불가다. c,e랑 결합(결합 할 수 있으면 끝내는게 맞는지 아니면 쭉 해봐?). 

그러면 음 bdf는 해볼 것도 없이 c,e랑은 결합이 안된다

 

음 즉 결합 쌍이 나오면 다른 결합 불가놈들도 결합 가능한 놈이랑 해볼 것도 없이 결합 불가인데.

음ㅋㅋ 이게 문제 푸는데 의미가 있나? 그냥 결합쌍 찾으면 그 두개는 아예 배제하면 뭐 다시 확인할 것도 없잔아. 

 

3은 별로 최적화할게 없는건가. 

 

그럼 1에서 최적화를 해보면...

전부 돌리고 전부 매치되는지 확인하기 전에 확인할 수 있는 방법이 있나. 굳이 ....?

 

음 왠지 빠르게 하는걸 생각하다가 해쉬를 이용한 방법을 계속생각했다.

 

 

3 2 2 2

1 2 2 3

1 3 1 3

2 2 3 1 이면

 

1: 4개

2: 7개

3: 5개

 

6(0,0) 6(0,1) 6(0,2) 5(0,3)

5(1,0) 6(1,1) 6(1,2) 7(1,3)

5(2,0) 7(2,1) 5(2,2) 7(2,3)

7(3,0) 5(3,1) 6(3,2) 6(3,3) 이걸 90도 회전해야 하는데 전에 해봤는데 음 다시 보니까 모르겠다.

 

0,0->0,3

0,1->1,3

1,1->1,2

3,0->0,0

3,1->1,0

i,j -> j,4-1-i

 

5: 5개

6: 7개

7: 4개

음 일단 475 , 574 개씩 갯수만 맞으면 되는거 아닌가? 아 아니구나 위치도 맞아야되지.

근데 4개는 4개 끼리 5는 5끼리 해서 457 457만이 매칭가능하다 일단은.

 

3이내다 예시처럼

즉 갯수만 세면 일단 가능성 높은거 끼리 확인할 수 있을듯!.!!

 

1. 숫자 3개의 갯수가 같아야 한다.

2. 같은 갯수인 높이끼리 더하면 셋이 같아야 한다. 같은 해쉬인덱스를 가지면 같은 애들이긴해 일단은.

2-1. 근데 여기서 링크드 리스트에서 두놈씩 비교해야 되는건가?

 

3. 이제 회전으로 위치도 같은지 확인하면 된다. -> 좀더 효율적으로 확인 가능할듯 위치만 확인하면 되니까.

457개면 4,5개의 위치끼리만 일치해도 7개인 위치는 당연히 일치할 수 밖에 없는거다.

3-1. 일치하면 어떻게해? 두개를 링크드 리스트에서 제거하고, saved 절약된 값을 더해주고. 

 

음 그런데 위치를 저장하기가 어렵다. ㅋㅋ 위치를 어떻게 저장하고 확인하지.??? 저장은 해도 회전이 너무 어려운데.

걍 이부분은 패스 못하겠어.

 

해쉬 맵으로 저장한다.

//세개 숫자 쌍이니까 1000보다 작다.

hash[1000] ;

 

 

#include<iostream>

using namespace std;

struct Block{
	//해당 높이가 몇개 있는지.
	int block_height[10] = { 0 };
	//해당 높이가 뭐있는지 3개.
	int height[3] = { 0 };
	int index;
	int arr[4][4];
	Block * prev= NULL,*next=NULL;
};

class BlocksHash {
private:
	Block * head[1000] = { NULL };
	Block * tail[1000] = { NULL };
	int Size[1000] = { 0 };
	int saved = 0;


public:
	//해당 모듈을 해쉬맵에 넣는다. 그런데 해쉬 인덱스가 오름차순으로 정렬해야 한다.
	void push(int module[][4]);

	//해당 인덱스의 리스트에서 첫째로는 3개높이의 갯수가 일치하는지 확인하고
	//일치하면 2개씩 비교(isMatch)한다.
	void compareList(int index);
	
	//두 블록안의 각 블록들의 위치들이 갯수에 맞게 일치하는지 확인한다.
	//위치에 따른 구현은 내가 못하겠어서 그냥 회전해서 전체를 비교하는 식으로 해야 
	//될거 같다.
	//근데 모듈을 여기 넣어도 되는건가....
	int isMatch(Block *a, Block*b);
	//해당 블록 해쉬맵에서 제거.
	void pop(Block*a);
	//모듈을 90도 회전한다.
	void rotate90(int module[][4]);
};

BlocksHash hash;
//이 부분 구현이 어렵다.. pop 했을때 연속성있게 두개를 비교하기가
void BlocksHash::compareList(int index) {
	Block* cur,*other;
	cur = head[index];
	other = head[index];
	while (cur->next != NULL) {
		while (other->next != NULL) {
			if (cur != other) {
				if (int tmp =isMatch(cur, other)) {
					Block*tmp1, *tmp2;
					
					tmp1 = cur->next;
					tmp2 = other->next;

					pop(cur);
					pop(other);

					cur = tmp1;
					other = tmp2;

					this->saved += tmp;

					break;
				}
				else {
					other = other->next;
				}
			}
		}
		cur = cur->next;
	}

}

void BlocksHash::push(int module[][4]) {
	int index;
	Block* node = new Block();
	//일단 인덱스를 구해야지.
	//1~457 일 수 있다.
	int nums[10] = { 0 };
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			nums[module[i][j]] ++;
		}
	}
	int exp= 1;
	int cnt = 0;
	for (int i = 0; i < 10; i++) {
		if (nums[i] != 0 && cnt <3) {
			index += nums[i] * exp;
			exp *= 10;
			node->height[cnt] = nums[i];
			cnt++;
		}
	}

	for (int i = 0; i < 4; i++) {
		for(int j=0;j<4;j++){
			node->arr[i][j] = module[i][j];
		}
	}

	//해당 인덱스에 원소가 없어.
	if (head[index] == NULL) {	
		head[index] = node;
		tail[index] = node;
	}
	//있다.
	else {
		node->prev = tail[index];
		tail[index]->next = node;
		tail[index] = node;
	}
}

void BlocksHash::pop(Block*a) {
	//맨 앞은 아니다.
	if (a->prev != NULL) {
		//맨 뒤는 아니다.
		if (a->next != NULL) {
			a->prev->next = a->next;
			a->next->prev = a->prev;
		}
		else {
			a->prev->next = NULL;
			tail[a->index] = a->prev;
		}
	}
	//맨 앞에 있는놈
	else {
		head[a->index] = a->next;
		a->next->prev = NULL;
	}

	delete a;
}

int dir_x[4] = { 0,1,0,-1 };
int dir_y[4] = { 1,0,-1,0 };

void BlocksHash::rotate90(int module[][4]) {
	int arr[4][4];
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			arr[j][3 - i] = module[i][j];
		}
	}
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			module[i][j] = arr[i][j];
		}
	}
}

int BlocksHash::isMatch(Block *a, Block*b) {
	bool match = true;
	//index가 같은건 이미 compareList함수에서 보장한다. 
	for (int k = 0; k < 4; k++) {
		
		match = true;
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				if (a->arr[i][j] != b->arr[i][j]) {
					match = false;
					break;
				}
			}
			if (match == false) {
				break;
			}
			if (match == true && i == 3) {
				return a->arr[0][0] + b->arr[0][0];
			}
		}
		rotate90(a->arr);
	}

}




int makeBlock(int module[][4][4])
{


	;
}

 내가 못하는 문제는 무엇일까?

음 이유를 분석해 보자.

내 생각에는

1. 문제를 내가 구현 가능한 수준에서 설계를 못한다. 

1-1. 그렇게 설계하면 구현을 못함 내가.

1-2. 생각은 맞는데 구현을 못하는건지 구현 계획 자체가 비효율적인건지....

-> 약간 이 둘을 타협하는 방법이 자주 답인거 같다.....

 

링크드리스트 순회하면서 노드 삭제하는건 좀 어렵다고 생각되서 그냥 flag를 주어서 체크한 놈이면 다시 확인 안하도록 했다.

 

음 결국 구현했는데 왜 틀리는거야~

에러 내가 찾긴 했는데 그래도...

#include<iostream>

using namespace std;
#define MAX 30000
#define NULL 0

struct Block{
	//해당 높이가 몇개 있는지.
	int block_height[10] = { 0 };
	//해당 높이가 뭐있는지 3개.
	int height[3] = { 0 };

	int blockIndex;
	int hashIndex;

	bool checked = false;

	Block * prev= NULL,*next=NULL;
};

class BlocksHash {
private:

	//14 1 1
	//2 4 10
	//1411
	Block * head[1500] = { NULL };
	Block * tail[1500] = { NULL };
	int Size[1500] = { 0 };
	int saved = 0;


public:
	//해당 모듈을 해쉬에 넣는다.
	void push(int index, int module[][4][4]);

	//해당 인덱스의 리스트에서 첫째로는 3개높이의 갯수가 일치하는지 확인하고
	//일치하면 2개씩 비교(isMatch)한다.
	void compareList(int hashIndex,int module[][4][4]);
	
	//두 블록안의 각 블록들의 위치들이 갯수에 맞게 일치하는지 확인한다.
	//위치에 따른 구현은 내가 못하겠어서 그냥 회전해서 전체를 비교하는 식으로 해야 
	//될거 같다.
	//근데 모듈을 여기 넣어도 되는건가....
	int isMatch(Block *a, Block*b,int module[][4][4]);
	//해당 블록 해쉬맵에서 제거.
	void pop(Block*a);
	//모듈을 90도 회전한다.
	void rotate90(int blockIndex,int module[][4][4]);
	int getSaved();
};

int BlocksHash:: getSaved() {
	return saved;
}

BlocksHash hashmap;
//이 부분 구현이 어렵다.. pop 했을때 연속성있게 두개를 비교하기가
void BlocksHash::compareList(int hashIndex, int module[][4][4]) {
	Block* cur,*other;
	
	if( head[hashIndex] != NULL ) {
		cur = head[hashIndex];
		other = head[hashIndex];
		while (cur != NULL) {
			if (cur->checked == false) {
				while (other != NULL) {
					if (cur != other && other->checked == false) {
						if (int tmp = isMatch(cur, other, module)) {
							cur->checked = true;
							other->checked = true;

							this->saved += tmp;

							break;
						}
					}
					
					other = other->next;
				}//while
			}
			cur = cur->next;
		}//while
	}//if

}

void swap(int *a, int *b) {
	int tmp = *a;
	*a = *b;
	*b = tmp;
}

void sort(int arr[]) {
	if (arr[0] > arr[1]) {
		swap(&arr[0], &arr[1]);
	}
	if (arr[1] > arr[2]) {
		swap(&arr[1], &arr[2]);
	}
	if (arr[0] > arr[1]) {
		swap(&arr[0], &arr[1]);
	}
}

void BlocksHash::push(int blockIndex, int module[][4][4]) {
	Block* node = new Block();
	//일단 인덱스를 구해야지.
	//1~457 일 수 있다.
	int nums[10] = { 0 };
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			nums[module[blockIndex][i][j]] ++;
		}
	}
	int exp = 1;
	int cnt = 0;
	int index = 0;

	int arr[3] = { 0 };
	//인덱스가 오름차순으로 만들어지는게 아니다 655처럼.656
	for (int i = 0; i < 10; i++) {
		if (nums[i] != 0 && cnt <3) {
			arr[cnt] = nums[i];
			cnt++;
		}
	}
	sort(arr);

	for (int i = 0; i < 3; i++) {
		index += arr[i] * exp;
		exp *= 10;
	}

	node->blockIndex = blockIndex;
	node->hashIndex = index;
	Size[index]++;


	//해당 인덱스에 원소가 없어.
	if (head[index] == NULL) {	
		head[index] = node;
		tail[index] = node;
	}
	//있다.
	else {
		node->prev = tail[index];
		tail[index]->next = node;
		tail[index] = node;
	}
}

void BlocksHash::pop(Block*a) {
	//맨 앞은 아니다.
	if (a->prev != NULL) {
		//맨 뒤는 아니다.
		if (a->next != NULL) {
			a->prev->next = a->next;
			a->next->prev = a->prev;
		}
		else {
			a->prev->next = NULL;
			tail[a->hashIndex] = a->prev;
		}
	}
	//맨 앞에 있는놈
	else {
		head[a->hashIndex] = a->next;
		a->next->prev = NULL;
	}

	delete a;
}

int dir_x[4] = { 0,1,0,-1 };
int dir_y[4] = { 1,0,-1,0 };

void BlocksHash::rotate90(int index,int module[][4][4]) {
	int arr[4][4];
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			arr[j][3 - i] = module[index][i][j];
		}
	}
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			module[index][i][j] = arr[i][j];
		}
	}
}

void printArr(int index, int module[][4][4]) {
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 4; j++) {
			cout << module[index][i][j]<<" ";
		}
		cout << "\n";
	}
	cout << "\n";
}
//왜 a,b의 블록인덱스가 같은게 이 함수로 들어오지

int BlocksHash::isMatch(Block *a, Block*b,int module[][4][4]) {
	bool match = true;
	int aIndex = a->blockIndex;
	int bIndex = b->blockIndex;
	//index가 같은건 이미 compareList함수에서 보장한다. 
	for (int k = 0; k < 4; k++) {
		
		match = true;
		for (int i = 0; i < 4; i++) {
			for (int j = 0; j < 4; j++) {
				if (module[aIndex][i][j] != module[bIndex][i][j]) {
					match = false;
					break;
				}
			}
			if (match == false) {
				break;
			}
			if (match == true && i == 3) {
				printArr(aIndex, module);
				printArr(bIndex, module);

				return module[aIndex][0][0] + module[bIndex][0][0];
			}
		}
//		printArr(aIndex, module);
		rotate90(aIndex,module);
//		printArr(aIndex, module);
	}
	return 0;
}




int makeBlock(int module[][4][4])
{
	for (int i = 0; i < MAX; i++) {
		hashmap.push(i, module);
	}
	for (int i = 0; i < 1500; i++) {
		hashmap.compareList(i, module);
	}

	return hashmap.getSaved();
}


 

 

다른 분이 블로그에 올린 해설을 보고 있다.

 

일단 내가 잘 못한게 같은 모양 블록 중에 높이가 긴거 끼리 먼저 매칭 시켜야 되는데 3개 면 높이 긴 2개를 해줘야 되잔아. 나는 정렬없이 그냥 아무거나 더했다.