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

시간 제한 메모리 제한
1초 1024MB

문제

N*N 크기의 격자 위에 칸마다 코인이 놓여있다.

푸앙이는 격자 위에 한 변의 길이가 1 이상 N이하인 정사각형 그물을 만들어 그 안의 코인을 얻을 수 있다. 하지만 그물이 포함하는 칸 개수의 제곱에 해당하는 코인을 지불해야 한다.

그물은 반드시 N*N크기의 격자 안에 완전히 포함 되어야 하고, 한 번 그물을 친 칸에는 다시 그물을 칠 수 없다.

푸앙이가 정확히 두 번의 그물을 쳐서 얻을 수 있는 코인의 최대 개수를 구하시오.

 

입력

첫 번째 줄에 정수 N(2 ≤ N ≤ 400)이 주어진다.

두 번째 줄부터 N개의 줄에는 칸에 놓여 있는 코인의 수가 1행부터 차례대로 주어진다. 칸에 놓여 있는 코인의 수는 10^9 이하의 양의 정수이다.

 

출력

첫 번째 줄에 푸앙이가 정확히 두 번의 그물을 쳐서 얻을 수 있는 코인의 최대 개수를 출력한다.

 


풀이

N의 범위가 400으로 매우 만만해 보입니다. 최대 크기의 격자인 400*400에 그물을 한 번 치는 경우의 수를 생각해 봅시다. 400*400으로 1가지, 399*399로 2*2가지, 398*398로 3*3가지,... 1*1로 400*400가지, 모두 더해보면 21,413,400가지입니다. 시도해 볼 만한 숫자이지만 그물을 두 번 쳐야 한다는 점이 문제입니다. 2천만 가지의 경우로 그물을 한 번 쳤을 때, 나머지 그물을 치는 경우를 모두 구해보면 경우의 수가 너무 커집니다.

 

문제에서 주어진 세 가지 조건을 봅시다. "한 변의 길이가 1이상 N이하인 정사각형 그물", "그물은 반드시 N*N크기의 격자 안에 완전히 포함", "한 번 그물을 친 칸에는 다시 그물을 칠 수 없다." 이 세 조건에 맞게 그물을 친다면 그물의 배치는 이런 특징을 갖게 됩니다.

두 개의 그물은 반드시 전체 격자를 상하, 또는 좌우로 분할한 두 개의 직사각형 안에 따로 포함됩니다. 이걸 두고 보면 전체 격자를 이렇게 분할해 볼 수 있습니다.

격자를 a, c와 b, d로 상하 분할하는 지점을 i로, a, b와 c, d로 좌우 분할하는 지점을 j라고 합시다. 이제 이 안에 두 개의 그물을 배치한다면 다음 두 가지 경우만 가능합니다.

  1. a, c에 하나의 그물을 배치하고 b, d에 나머지 하나를 배치
  2. a, b에 하나의 그물을 배치하고 c, d에 나머지 하나를 배치

각각의 그물이 어디에 어떻게 있어야 하는가를 따로 구한다면 앞서 말했듯이 시간초과입니다. 하지만 a, b 직사각형에서 만들어지는 가장 좋은 그물의 정보가 이미 있다면 400개의 i와 400개의 j에 대해 O(400*400)의 시간 안에 가장 좋은 두 개의 그물을 찾아낼 수 있습니다. 그 전에 전체 격자를 상하 또는 좌우로 나누어 분할했을 때 분할 된 직사각형 안에서 만들 수 있는 가장 좋은 그물로 얻는 코인의 양을 다음 배열로 관리합니다.

left[i] = i번째 칸을 기준으로 왼쪽 직사각형에서 얻는 최적해
right[i] = i번째 칸을 기준으로 오른쪽 직사각형에서 얻는 최적해
up[j] = j번째 칸을 기준으로 위쪽 직사각형에서 얻는 최적해
down[j] = j번째 칸을 기준으로 아래쪽 직사각형에서 얻는 최적해

그럼 i를 기준으로 좌우 분할한다면 left[i]와 right[i + 1]로 가장 좋은 두 개의 그물을 찾을 수 있습니다. 상하 분할한다면 up[i], down[i + 1]입니다.

 

왼쪽 윗 칸이 a + 1, b + 1로 시작되고 한 변의 길이가 k인 정사각형의 그물을 생각해 봅시다.

주황색 영역에 친 그물로 얻는 코인을 coin(a, b, k)라고 하겠습니다. 왼쪽 위 끝 칸이 a, b가 아닌 이유는 이게 점이 아니라 칸이기 때문입니다. a+1로 시작해야 a+k가 k번째 칸이 됩니다. 그럼 이 그물로 인해 전체 격자는 4가지 방법으로 분할되고, 그물이 포함되는 배열은 이렇습니다.

1) 그물의 왼쪽변으로 좌우 분할, 그물은 right[b + 1]에 포함
2) 그물의 오른쪽변으로 좌우 분할, 그물은 left[b + k]에 포함
3) 그물의 윗변으로 상하 분할, 그물은 down[a + 1]에 포함
4) 그물의 아랫변으로 상하 분할, 그물은, up[a + k]에 포함

그럼 이제 대강 2천만 가지의 그물을 모두 만들어보며 left, right, up, down 배열의 최적해를 각각 정리하고 400*400가지의 분할 지점에 따라 좌우, 상하 직사각형의 두 그물을 확인해보면 O(N^3)으로 가장 좋은 두 개의 그물을 찾을 수 있습니다.

 

정답 코드

더보기
import sys
input = sys.stdin.readline
def solution():
    n = int(input())
    coins = [list(map(int, input().split())) for _ in range(n)]
    quad = [i ** 4 for i in range(400)]
    
    # 그물로 얻는 코인을 확인
    def net(a, b, k):
        ret = -quad[k]
        ret += coins[a + k][b + k]
        if a >= 0 : ret -= coins[a][b + k]
        if b >= 0: ret -= coins[a + k][b]
        if a >= 0 and b >= 0 : ret += coins[a][b]
        return ret
        
    # 좌, 우, 상, 하의 최적 그물을 관리할 배열
    l = [0] * n
    r = [0] * n
    u = [0] * n
    d = [0] * n
    
    # 격자의 누적합 생성
    for i in range(n):
        for j in range(1, n):
            coins[i][j] += coins[i][j - 1]
    for j in range(n):
        for i in range(1, n):
            coins[i][j] += coins[i - 1][j]
    
    # 가능한 모든 그물로 분할 영역의 최적해 구하기
    for k in range(1, 400):
        for i in range(-1, n - k):
            for j in range(-1, n - k):
                sq = net(i, j, k)
                u[i + k] = max(u[i + k], sq)
                d[i + 1] = max(d[i + 1], sq)
                l[j + k] = max(l[j + k], sq)
                r[j + 1] = max(r[j + 1], sq)
    for i in range(1, n):
        if l[i] < l[i - 1]: l[i] = l[i - 1]
        if u[i] < u[i - 1]: u[i] = u[i - 1]
    for i in range(n - 2, -1, -1):
        if r[i] < r[i + 1]: r[i] = r[i + 1]
        if d[i] < d[i + 1]: d[i] = d[i + 1]
        
    # 모든 칸을 기준으로 상하 또는 좌우 분리된
    # 두 직사각형에서 최적해 탐색
    res = 0
    for i in range(1, n):
        if l[i - 1] + r[i] > res:
            res = l[i - 1] + r[i]
        if u[i - 1] + d[i] > res:
            res = u[i - 1] + d[i]
    print(res)
solution()

 

 

전라남도교육지원청