알고리즘 문제 풀기

백준 - 1725번 히스토그램,6549 히스토그램에서 가장 큰 직사각형

studying develop 2020. 1. 3. 13:51

이 문제를 스택으로 해결했었다. 그런데 음 스택으로도 다시 풀어보고, 분할 정복으로도 풀 수 있던데 두가지로 풀어보겠다. 이전에 스택했으니 일단 분할정복으로 하자.

 

보통 분할 정복은 절반씩 분할하는거 같다. 계속 분할해서 한개가 되고 두개로 합칠때 음?

 

어떻게 생각해야되는 걸까....

 

음 분할해서 해당 영역의 가장 큰 넓이를 찾으면 된다고 생각한다. 그리고 이제 두개를 합치면 되는거 같은데, 그렇다면 합칠때 양쪽 중에 더 작은 높이 * 양쪽 갯수  랑 두개 영역을 비교해서 가장 큰 넓이를 선택하게 한다.

 

이때 합병하면서 저장해야 되는 값은 가장 작은 높이, 합친 넓이이다. 가장 작은 높이랑 갯수를 알면 왼쪽 오른쪽을 합칠때 합쳐질 넓이를 알 수 있다.

 

재귀함수를 사용할텐데 매개 변수로 시작,끝 인덱스를 넘겨주면 갯수는 알 수 있다. 그럼 리턴으로 

 

음 위의 방법으로는 답을 찾을 수 없다.

 

2 1 4 5 1 3 3의 경우 가운데 45가 합쳐지기가 어렵다.

 

5 1 3 3에서

 

5 1 중에 5 , 3 3은 같으니 합쳐서 6으로 하고 다시 이 두개를 합치면

 

6을 고르면 

 

4 6 이랑 합치면 답인 9가 나올 수 가 없다. 즉 그냥 큰걸로 고르면 안된다.

 

근데 리턴은 가장 큰 영역을 하는게 맞다. 분할의 의미가 그러니까, 그러면 정복할때 그냥 무조건 큰것을 고르는게 아니라 경우를 따져봐야 할거 같다. 아 위에서 처럼 그 영역에서 가장 작은 걸로 양쪽을 합쳐보는게 아니라 양쪽에 맞닿아 있는 각각 한 기둥중 높이가 더 작은 것으로 합쳐봐야 한다. 왜냐하면 그 둘이 합쳐지지 않으면 어차피 다른놈들로도 합쳐지지 않는다.

 

 

 

음 위처럼 구현했는데 틀렸다.

 

#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<vector>
#include<iostream>
#include<stdlib.h>
#include<algorithm>

using namespace std;

int N;
vector<int> vec;


int mergeNconquer(int st, int dt) {
	if (dt - st == 0) {
		return vec[st];
	}

	int Left, Right;
	int bigger;
	int merged=0;
	int smaller;

	int l1, r1;
	int l2, r2;
	int dis = dt - st;

	l1 = st;
	r1 = st + dis / 2;
	l2 = st + dis / 2 + 1;
	r2 = dt;



	Left = mergeNconquer(l1,r1 );
	Right = mergeNconquer(l2,r2);

	bigger = max(Left, Right);

	smaller = min(vec[r1], vec[l2]);

	for (int i = l2; i <= r2; i++) {
		if (vec[i] >= smaller)
			merged += smaller;
		else {
			break;
		}
	}
	for (int i = r1; i >= l1; i--) {
		if (vec[i] >= smaller) {
			merged += smaller;
		}
		else {
			break;
		}
	}

	bigger = max(bigger, merged);

	return bigger;
}


int main() {
	freopen("input.txt", "r",stdin);

	cin >> N;
	for (int i = 0; i < N; i++) {
		int tmp;
		cin >> tmp;
		vec.push_back(tmp);
	}
	cout << mergeNconquer(0, N - 1);
}

 

원인으로 의심되는 이유는 바로 맞닿은 두 기둥보다 더 작은 넓이로 합쳐도 될거 같은데.....?

 

8
2
2
2
5
6
2
3
3

 

역시 이게 안된다. 이거 답은 16인데 10이 나온다. 그러면 맞닿은 애들 양쪽 모두의 블록을 기준으로 할 수는 없다. 시간초과 나올텐데 그냥 막하는거랑 똑같지 않나.

 

아 그래서 아래처럼 전부에 대해서 하니까 시간 초과가 뜬다.

 

#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<vector>
#include<iostream>
#include<stdlib.h>
#include<algorithm>

using namespace std;

int N;
vector<int> vec;


int mergeNconquer(int st, int dt) {
	if (dt - st == 0) {
		return vec[st];
	}

	int Left, Right;
	int bigger;
	int merged=0;
	int smaller;

	int l1, r1;
	int l2, r2;
	int dis = dt - st;

	l1 = st;
	r1 = st + dis / 2;
	l2 = st + dis / 2 + 1;
	r2 = dt;



	Left = mergeNconquer(l1,r1 );
	Right = mergeNconquer(l2,r2);

	bigger = max(Left, Right);

	for (int k = st; k <= dt; k++) {
		merged = 0;
		smaller = vec[k];
		for (int i = l2; i <= r2; i++) {
			if (vec[i] >= smaller)
				merged += smaller;
			else {
				break;
			}
		}
		for (int i = r1; i >= l1; i--) {
			if (vec[i] >= smaller) {
				merged += smaller;
			}
			else {
				break;
			}
		}
		bigger = max(bigger, merged);
	}



	return bigger;
}


int main() {
	freopen("input.txt", "r",stdin);

	cin >> N;
	for (int i = 0; i < N; i++) {
		int tmp;
		cin >> tmp;
		vec.push_back(tmp);
	}
	cout << mergeNconquer(0, N - 1);
}

 

음 어떻게 해야 하지.....

 

두 영역을 합칠때 가장 큰 경우는 세가지로 분류 할 수 있을거 같다.

1. 만나는 가운데 기둥들 포함해서 가장 큰 경우

2. 만나는 기둥 빼고 오른쪽에서 가장 큰 경우

3. 만나는 기둥 빼고 왼쪽에서 가장 큰 경우.

4. 왼쪽 오른쪽에서 특정 길이로 가운데 포함에서 쭉 더할 경우. -> 특정 길이를 일일이 다해볼 수는 없다. 그래서 시간 초과가 나왔던 것.

 

아 궁금해서 찾아보니까 

양쪽에 걸친 경우는, 영역을 높이가 양쪽 중 높이가 더 높은 부분으로 확장해가면서 넓이의 최대값을 저장해가면 된다.

이렇다는데, 저 말이 무슨 말인지는 알겠는데, 이해는 안되고 내 생각에는 높이 기준으로 해야되는데 왜 저렇게 하는지 모르겠다.

 


 

이건 전에 풀은 스택 풀이다.

#define _CRT_SECURE_NO_WARNINGS
#include<cstdio>
#include<iostream>
#include<string.h>
#include<string>
#include<stack>
#include<vector>
using namespace std;

vector<pair<int, int>> vec;
stack<pair<int,int>> st;

int N;
int arr[100001];
int dp[100001];
//가장 큰수는 -1로 정하기
//가장 오론쪽도 -1
//나머지는 왼쪽 기준 쭉가다가 자기보다 큰수만나면 큰수로 쭉 채우면되.

int main() {
	cin >> N;
	
	for(int i=0;i<N;i++){
		scanf("%d", &arr[i]);
		vec.push_back(make_pair(0,0));
	}

	st.push(make_pair(arr[0],0));
	vec[0].first = 0;
	for (int i = 1; i < N; i++) {
		if (st.top().first > arr[i]) {
//			vec[st.top().first].first = st.top().first;	//left

			while(st.top().first >= arr[i]) {
				//vec[st.top().second].first = st.top().first;	//left
				vec[st.top().second].second = i;	//right
				st.pop();

				if (st.empty() ) {
					break;
				}
				vec[i].first = st.top().second+1;
			}
			st.push(make_pair(arr[i], i));

		}
		else if (st.top().first <= arr[i])
		{
			st.push(make_pair(arr[i],i));
			vec[st.top().second].first = i;
		}
	}

	while (!st.empty()) {
		vec[st.top().second].second = N;
		st.pop();
	}
	int max = 0;
	int tmp;
	for (int i = 0; i < N; i++) {
	//	printf("%d: %d %d\n",arr[i], vec[i].first, vec[i].second);
		if (max < (tmp =((vec[i].second - vec[i].first)*arr[i])) ) {
			max = tmp;
		}
	}

	cout << max<<"\n";
}

 

 


 

6549 히스토그램에서 가장 큰 직사각형

28일 만에 다시 풀어본다.ㅋㅋ

 

		for (int i = 0; i < N; i++) {
			for (int j = i; j < N; j++) {
				long long int temp = (j - i + 1)*find(0, N - 1, 1, i, j);
				if (temp > maxv) {
					maxv = temp;
				}
			}
		}

코드의 이 부분에서 이중 반복문이라 안된거 같다.

 

https://kyunstudio.tistory.com/102 이분 코드 보고 풀었다.

 

양쪽을 병합할때 옆으로 확장하는 것을 잘못알아 듣고 했다. 근데 왜 이런식으로 하는지 모르겠네...으으

 

음 답을 알고나서 생각해 보니. 맞닫는 부분 양쪽으로 확장하면서 최대 넓이를 구해야 하는데 그것을 구하는 방법은 양쪽중에 가장 큰 사각형쪽으로 먼저 커지고 차차 다른 작은 사각형으로도 확장하면 결국 가장 높을때를 기준으로한 넓이와 가장 작을때를 기준으로 한 넓이를 둘다 구할 수 있게 된다. 

 

나는 이것을 해결하려고 병합할때 각각의 모든 기둥에 대해 넓이를 구해보는 brute force 방법만을 생각했는데, 이렇게 양쪽으로 커다란 사각형으로 먼저 확장하는 방식을 사용하면 ( 대신 minHeight는 확장함에 따라 가장 작은 것을 골라야 한다.)

 

 

 

그리고 원래의 내 코드로는 까다로웠던 반례이다.

14
3 2 1 3 2 1 1 0 2 1 1 0 1 2

 

#define _CRT_SECURE_NO_WARNINGS
#define NUMBER  100000
#define MAXV 1000000000001
#include <iostream>
#include <vector>

using namespace std;
int N, M;
vector<long long int> arr;

long long int min_long(long long int a, long long int b)
{
	if (a < b) {
		return a;
	}
	else {
		return b;
	}
}

long long int max_long(long long int a, long long int b)
{
	if (a < b) {
		return b;
	}
	else {
		return a;
	}
}

long long int find(int start, int end) {
	int mid = (start + end) / 2;

	if (start == end) {
		return arr[start];
	}

	long long int leftBlock = find(start, mid);
	long long int rightBlock = find(mid + 1, end);

	long long int middleBlock=0;

	long long int minHeight;
	int index;
	if (arr[mid] < arr[mid + 1])
	{
		index = mid;
		minHeight = arr[mid];
	}
	else {
		index = mid+1;
		minHeight = arr[mid + 1];
	}

	int cnt=1;
	int low, high;
	low = mid;
	high = mid;

	while (start <= low && high <= end) {
		if (low-1 >= start &&(high == end || arr[low - 1] > arr[high + 1])) {
			low--;
			minHeight = min_long(minHeight, arr[low]);
			cnt++;
		}
		else if(high+1 <= end){
			high++;
			minHeight = min_long(minHeight, arr[high]);
			cnt++;
		}
		else {
			break;
		}
		middleBlock = max_long(cnt*minHeight, middleBlock);
	}

	/*
	for (int i = index; i <= end&& arr[i] >= minHeight; i++) {
		cnt++;
		minHeight = min_long(arr[i], minHeight);
	}
	for (int i = index - 1; i >= start&& arr[i] >= minHeight; i--) {
		cnt++;
		minHeight = min_long(arr[i], minHeight);
	}
	middleBlock = cnt * minHeight;
	*/

	long long int answer = max_long(leftBlock, middleBlock);
	answer = max_long(answer, rightBlock);

	return answer;
}

int main() {
	freopen("input.txt" , "r",stdin);

	ios_base::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	cin >> N;
	while (N) {
		long long int maxv = 0;

		for (int i = 0; i < N; i++)
		{
			long long int tmp;
			cin >> tmp;
			arr.push_back(tmp);
		}
		//init(0, N - 1, 1);
		

		cout << find(0, N - 1) << "\n";
		cin >> N;

		arr.clear();
	}

	return 0;
}