HTD
[Python] 백준 6549번: 히스토그램에서 가장 큰 직사각형 본문
https://www.acmicpc.net/problem/6549
6549번: 히스토그램에서 가장 큰 직사각형
입력은 테스트 케이스 여러 개로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있고, 직사각형의 수 n이 가장 처음으로 주어진다. (1 ≤ n ≤ 100,000) 그 다음 n개의 정수 h1, ..., hn (0 ≤ hi ≤
www.acmicpc.net
import sys
import math
input = sys.stdin.readline
sys.setrecursionlimit(1000000)
def get_tree_length(N):
    if N & (N-1) == 0:
        return 2*N
    else:
        return pow(2, math.ceil(math.log(N, 2)) + 1)
def initialize_segtree(index, start, end):
    if start == end:
        tree[index] = start
        return
    mid = (start + end)//2
    initialize_segtree(index*2, start, mid)
    initialize_segtree(index*2+1, mid+1, end)
    # Returns lower one
    if nums[tree[index*2]] < nums[tree[index*2+1]]:
        tree[index] = tree[index*2]
    else:
        tree[index] = tree[index*2+1]
def query_segtree(index, start, end, left, right):
    # For an exception
    if right < start or end < left:
        return -1
    if left <= start and end <= right:
        return tree[index]
    mid = (start+end)//2
    l = query_segtree(index*2, start, mid, left, right)
    r = query_segtree(index*2+1, mid+1, end, left, right)
    # Handle out of range exception
    if l == -1 or r == -1:
        return max(l, r)
    else:
        # Returns lower one
        if nums[l] < nums[r]:
            return l
        else:
            return r
def get_square_area(start, end):
    if start == end:
        return nums[start]
    # Get lowest height
    index = query_segtree(1, 1, nums[0], start, end)
    areas = [(end-start+1)*nums[index]]
    # Is in range?
    if index-1 >= start:
        areas.append(get_square_area(start, index-1))
    if index+1 <= end:
        areas.append(get_square_area(index+1, end))
    return max(areas)
if __name__ == '__main__':
    # Do while
    nums = list(map(int, input().split()))
    while nums[0] != 0:
        # Initialize segment tree
        tree = [0]*get_tree_length(nums[0])
        initialize_segtree(1, 1, nums[0])
        # Solution
        print(get_square_area(1, nums[0]))
        # Get next input
        nums = list(map(int, input().split()))
get_square_area() 함수에서, 가장 낮은 곳의 인덱스를 가져와서(세그먼트 트리를 이용), 그것을 기준으로 앞뒤로 자른다.

위 사진에서 가장 큰 직사각형의 후보는 총 3개가 있다. 각각 가장 낮은 높이에서의 직사각형, 파란색 왼쪽 직사각형, 파란색 오른쪽 직사각형이다. 여기서 파란색 구간에 get_square_area()로 재귀 함수를 호출해주는 식이며, 마지막으로 이 3개의 넓이 중 가장 큰 것을 리턴해준다.
앞서 언급하였듯이 Segment Tree를 통해 가장 낮은 곳의 인덱스를 가져와준다. 이를 위해 세그먼트 트리를 응용하여 설계해야하며, 크게 어려운 점은 없으니 부족한 설명은 주석이 채워주리라 믿는다.
이 문제에서 가장 중요한 건, 가장 작은 곳을 기준으로 나누어 푼다는 것이다. 나는 세그먼트 트리에 어떤 정보를 담아내서 query로 바로 가장 큰 직사각형의 넓이를 구할 수 있는 줄 알고 한참을 헤맸다.
Stack을 이용한 최적의 풀이(monotonic stack)
import sys
input = sys.stdin.readline
def solution(nums):
    res = 0
    length = nums[0]
    stack = [0]  # 인덱스를 저장
    nums[0] = 0  # 반복문 초기에 nums[stack[0]], 즉 nums[0]과 값을 비교하는데, 이에 대한 처리임
    nums.append(0)  # 마지막 인덱스에서의 연산을 위함
    for i in range(1, length+2):
        while nums[stack[-1]] > nums[i]:
            index = stack.pop()
            res = max(res, (i - (stack[-1] + 1))*nums[index])
        stack.append(i)
    return res
if __name__ == '__main__':
    nums = list(map(int, input().split()))
    while nums[0] != 0:
        print(solution(nums))
        nums = list(map(int, input().split()))'Algorithm > 백준' 카테고리의 다른 글
| [Python] 백준 1904번: 01타일 (0) | 2021.09.29 | 
|---|---|
| [Python] 백준 9184번: 신나는 함수 실행 (0) | 2021.09.27 | 
| [Python] 백준 14289번: 본대 산책3 (0) | 2021.09.24 | 
| [Python] 인접행렬의 제곱(백준 12850번: 본대 산책 2) (0) | 2021.09.24 | 
| [Python] 백준 9527번: 1의 개수 세기 (0) | 2021.09.17 | 
 
                  