Algorithm/문제풀이

[백준] 6549번:히스토그램에서 가장 큰 직사각형

지미닝 2024. 8. 26. 18:37

문제 설명

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

 

히스토그램은 직사각형 여러 개가 아래쪽으로 정렬되어 있는 도형이다. 각 직사각형은 같은 너비를 가지고 있지만, 높이는 서로 다를 수도 있다. 예를 들어, 왼쪽 그림은 높이가 2, 1, 4, 5, 1, 3, 3이고 너비가 1인 직사각형으로 이루어진 히스토그램이다.

히스토그램에서 가장 넓이가 큰 직사각형을 구하는 프로그램을 작성하시오.

 
입력은 테스트 케이스 여러 개로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있고, 직사각형의 수 n이 가장 처음으로 주어진다. (1 ≤ n ≤ 100,000) 그 다음 n개의 정수 h1, ..., hn (0 ≤ hi ≤ 1,000,000,000)가 주어진다. 이 숫자들은 히스토그램에 있는 직사각형의 높이이며, 왼쪽부터 오른쪽까지 순서대로 주어진다. 모든 직사각형의 너비는 1이고, 입력의 마지막 줄에는 0이 하나 주어진다.

 

 

- 그냥 단순히 히스토그램에서 가장 넓이가 큰 직사각형을 구하면 된다.

 

문제 풀이

처음에는 3가지 case를 분류했다.

당장 다음 높이가 stack의 top보다 큰경우, 같은경우, 작은경우로 나누어서 처리했었다.

 

1. 큰 경우?

- 스택에 있는 모든 사각형의 넓이를 + 1해준다.

- 새로운 높이가 추가된다. push({height,1})

 

2. 같은 경우

- 모두 길이 + 1

 

3. 작은 경우

- 스택에 있는 큰애들은 다 지워버린다.

- 여태 가로길이만큼 새로 push.

 

그래서 아래 코드가 나왔다. 논리적으로도 약간 모순이 있을거같은데 일단 시간초과로 실패했다.

#include <bits/stdc++.h>
using namespace std;
int main(void) {
ios::sync_with_stdio(0);
cin.tie(0);
while (true) {
int n;
long long ans = 0;
cin >> n;
if (n == 0) break;
stack<pair<long long, long long>> s; //height, 가로 넓이;
for (int i = 0; i < n; i++) {
long long height;
cin >> height;
if (s.empty()) { // 비었을 때
s.push({height, 1});
ans = max(ans, 1 * height);
continue;
}
if (height < s.top().first) {
int cur = s.top().second;
ans = max(ans, s.top().first * s.top().second);
s.pop();
s.push({height, cur + 1}); // 가로의 넓이의 + 1와 좀전에 받은 높이.
ans = max(ans, s.top().first * s.top().second);
}
// 같은 높이
else if (s.top().first == height) {
int cur = s.top().second;
s.pop();
s.push({height, cur + 1}); // 가로 +1 해주기
ans = max(s.top().first * s.top().second, ans);
}
// 더 높은게 들어온 경우...
else {
stack<pair<long long, long long>> t;
while (!s.empty()) {
if(s.top().first > height){
s.pop();
continue;
}
t.push({s.top().first, s.top().second + 1}); // 원래 있던거 +1 해서 넣어주기.
s.pop();
}
while (!t.empty()) {
s.push(t.top());
ans = max(s.top().first * s.top().second, ans);
t.pop();
}
// 새로운거 추가.
s.push({height, 1});
}
}
cout << ans << '\n';
}
}

 

코드도 너무 길고 답도 없다.

 

조금 관점을 바꿔야했다.

 

  1. 모든 높이를 순회하며 스택에 인덱스를 저장하자.  (인덱스를 알면 높이도 자연스레 알 수 있다.)
  2. 현재 높이가 스택의 top에 저장된 것보다 작을 경우에는 stack에서 pop하여 해당 높이의 최대 넓이를 계산하자.
    • 이때 스택의 top 다음 인덱스부터 현재 인덱스까지의 거리가 넓이가 된다.

이렇게 되면 시간 복잡도도 O(n)으로 확실히 줄어듦을 알 수 있다.

 

애초에 스택에 든 것의 전제는 아직 넓이가 끊기지 않은 놈들이다. 즉 for문에서 i가 상태라고 할 때, i상태에서 아직 끊기지 않은 사각형들이기에 굳이 width를 둘 필요도 없고, 넓이 계산에 있어서 width의 시작점을 고민하는 낭비를 제거할 수 있었다.

#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
while (true) {
int n;
cin >> n;
if (n == 0) break;
vector<int> heights(n);
for (int i = 0; i < n; i++) cin >> heights[i];
stack<int> s;
long long max_area = 0;
for (int i = 0; i <= n; i++) {
// 현재 높이 처리
int h = (i == n) ? 0 : heights[i];
while (!s.empty() && heights[s.top()] > h) {
int height = heights[s.top()];
s.pop();
int width;
if (s.empty()) {
width = i;
} else {
width = i - s.top() - 1;
}
max_area = max(max_area, (long long)height * width);
}
s.push(i);
}
cout << max_area << '\n';
}
return 0;
}

 

확연히 반복되는 스택 연산이 줄어들어 나름 괜찮아졌다.

 

 

어렵다...