i < j < k < l을 O(N)에 도는 미친 다이나믹 프로그래밍 테크닉
15487번: A[j]-A[i]+A[l]-A[k] (acmicpc.net)
크기 N인 주어진 배열 A에서 i < j < k < l인 순서쌍 (i,j,k,l)에 대하여 A[j] - A[i] + A[l] - A[k]의 최댓값을 찾는 문제
N은 최대 1000000이라 4중 for문으로 찾는건 당연히 불가능하다
A[j] - A[i]의 최댓값과 A[l] - A[k]의 최댓값을 나눠서 구하면 좋을 것 같다
근데 i < j일때, A[j] - A[i]의 최댓값도 O(N)에 구하는게 쉽지 않아보인다
어떻게 구할 수 있을까?
현재 0 ~ j 구간을 보고 있을때 A[j] - A[i]의 최댓값은, A[j] - (A[0] ~ A[j-1]중 최솟값)
dp[j] = i < j, i = 0,1,2,..,j-1인 모든 (i,j)에 대해 A[j] - A[i]의 최댓값이라고 정의하자.
j = 0, 1, 2, 3, ... 가면서 A[j]의 최솟값을 min_value로 항상 갱신해놓는다면,
현재 j에 대해 0~j-1까지 최솟값은 min_value이고 그러므로 A[j] - min_value로 최댓값을 구할 수 있다.
이를 dp[j]에 저장해놓고 다음 j+1로 가는데.. 그러면 A[j+1] - min_value를 구하면 되겠지만
i < j인 (i,j)에 대하여 A[j] - A[i]의 최댓값을 dp[j]로 정의하는데
이전에 구해놓은 dp[j]가 더 클수 있기 때문에, dp[j]과 A[j+1] - min_value를 비교해서 더 큰 값으로 갱신하면 된다
dp1[j] = max(dp1[j-1], A[j] - min_value)로 한다면, 모든 0~j구간의 (i,j)에 대한 최댓값을 dp1[j]에 저장할 수 있다
min_value = A[0]
#dp1[j] = A[j] - A[i]의 최댓값, i < j, i = 0,1,2,..,j-1
for j in range(1,n):
if dp1[j-1] > A[j] - min_value:
dp1[j] = dp1[j-1]
else:
dp1[j] = A[j] - min_value
if A[j] < min_value:
min_value = A[j]
똑같은 방법으로
dp[l] = k < l인 (k,l)에 대해 A[l] - A[k]의 최댓값으로 정의하면 구할 수 있을 것이다
근데 그렇게 하면 이제 문제는
i < j < k < l인 (i,j,k,l)에 대하여 A[j] - A[i] + A[l] - A[k]의 최댓값을 구하는게 문제다
i < j인 A[j] - A[i]의 최댓값과 k < l인 A[l] - A[k]의 최댓값은 알고 있는데... 이게 연결이 안된다
그러면 조금 생각을 바꿔서
dp[k] = k < l, l = k+1,k+2,...,n-1인 모든 (k,l)에 대해 A[l] - A[k]의 최댓값으로 정의해보자
그러면 k = n-1,n-2,...0으로 이동하면서 항상 l = k+1,k+2,..,n-1에서 A[l]의 값을 최댓값 max_value로 갱신해놓으면
A[l] - A[k]의 최댓값은 max_value - A[k]로 구할 수 있다
이를 dp[k] = max_value - A[k]로 저장해두고, 다음 k-1로 이동하면
max_value - A[k-1]과 dp[k]중 더 큰 값을 dp[k-1]에 저장하면 될 것이다.
dp[k] = max(dp[k+1], max_value - A[k])로 하면 k~n-1의 모든 (k,l)에 대하여 A[l] - A[k]의 최댓값을 저장할 수 있다
#dp2[k] = A[l] - A[k]의 최댓값 k < l, l = k+1,k+2,...,n-1
max_value = A[n-1]
for k in range(n-2,-1,-1):
if dp2[k+1] > max_value - A[k]:
dp2[k] = dp2[k+1]
else:
dp2[k] = max_value - A[k]
if A[k] > max_value:
max_value = A[k]
다음과 같이 0~j 구간의 A[j] - A[i]의 최댓값 dp1[j]와 k ~ n-1 구간의 A[l] - A[k]의 최댓값 dp2[k]를 알고 있다
그러므로 x = 0,1,2,..,n-2에 대하여 dp1[x] + dp2[x+1]은 무엇을 의미할까
i = 0~x-1인 모든 i에 대하여 A[x] - A[i]의 최댓값 + l = x+2,x+3,...,n-1인 모든 l에 대하여 A[l] - A[x+1]의 최댓값
i < x < x+1 < l인 모든 (i,x,x+1,l)에 대하여 A[x] - A[i] + A[l] - A[x+1]의 최댓값
근데 이렇게 하니까 i < j < k < l인 (i,j,k,l)에 대해 구해야하는데 i < j < j+1 < l인 (i,j,j+1,l)에 대해 구한것 같다
근데 그게 아니라
dp1[j] = i < j인 (i,j)에 대해 A[j] - A[i]의 최댓값
예를 들어 j = 3이라고 한다면 dp1[3]은... 0~3 구간의 A[j] - A[i]의 최댓값
0~3구간이라는건 (0,3), (1,3), (2,3)의 경우에 대해 A[j] - A[i]의 최댓값을 구한게 아니라
(0,1), (0,2), (0,3), (1,2), (1,3), (2,3)의 모든 경우에 대해 A[j] - A[i]의 최댓값을 구했다는 것
왜냐하면 dp1[j]를 구할때 dp1[j] = max(dp1[j-1], A[j] - min_value)로 이전 j-1과 비교해서 최댓값을 구했으니까
따라서 모든 i = 0,1,2,..,n-2에 대하여 dp1[i] + dp2[i+1]의 최댓값을 구한다면...
dp1[i]는 모든 i < j에 대해 최댓값을 구한거고 dp2[i+1]은 모든 j < k < l에 대해 최댓값을 구한 것이 된다
#i < j < k < l인 (i,j,k,l)에 대하여 A[j] - A[i] + A[l] - A[k]의 최댓값을 구하는 문제
n = int(input())
A = list(map(int,input().split()))
#A는 양수인데, A[j] - A[i]는 A[j] = 0, A[i] = 10000..이라 음수가 가능
INF = 10000000
dp1 = [-INF]*(n+1)
dp2 = [-INF]*(n+1)
#A[j] - A[i]의 최댓값 + A[l] - A[k]의 최댓값으로 나눠서 문제를 해결
min_value = A[0]
#dp1[j] = i < j, i = 0,1,2,..,j-1인 모든 (i,j)에 대하여 A[j] - A[i]의 최댓값
#j = 0,1,2,...,n-1일때 각각 A[i]의 최솟값에 대해 A[j] - (min_value)
#j가 커지면서 A[i]의 최솟값을 갱신
#dp1[j] = max(dp1[j-1], A[j] - min_value)하면 0~j 구간의 모든 (i,j)에 대해 최댓값
for j in range(1,n):
if dp1[j-1] > A[j] - min_value:
dp1[j] = dp1[j-1]
else:
dp1[j] = A[j] - min_value
if A[j] < min_value:
min_value = A[j]
#dp2[k] = k < l, l = k+1,k+2,...,n-1인 모든 (k,l)에 대해 A[l] - A[k]의 최댓값
#k = n-1,n-2,n-3,...,0에 대하여 (A의 최댓값) - A[k]
#k가 작아지면서 A[k]의 최댓값을 갱신
#dp2[k] = max(dp2[k+1], max_value - A[k])하면 k~n-1 구간의 모든 (k,l)에 대해 최댓값
max_value = A[n-1]
for k in range(n-2,-1,-1):
if dp2[k+1] > max_value - A[k]:
dp2[k] = dp2[k+1]
else:
dp2[k] = max_value - A[k]
if A[k] > max_value:
max_value = A[k]
answer = -INF
#dp1은 0~j구간 dp2는 k~n-1구간을 커버하므로
#dp1[i]+dp2[i+1]은 모든 i < j < k < l인 (i,j,k,l)에 대해 A[j]-A[i] + A[l]-A[k]의 최댓값이 된다
for i in range(n-1):
if dp1[i] + dp2[i+1] > answer:
answer = dp1[i]+dp2[i+1]
print(answer)
'알고리즘 > 다이나믹 프로그래밍' 카테고리의 다른 글
가방이 여러개인 multiple 0-1 knapsack(배낭 문제) 해법 배우기 (0) | 2024.07.03 |
---|---|
0-1 knapsack 배낭 문제 해법 반드시 알아야하는 디테일(물건을 중복해서 담는 경우) (0) | 2024.07.03 |
다이나믹 프로그래밍으로 구하는 복수순열2 - 주어진 개수의 알파벳들만으로 만들 수 있는 문자열의 개수 (0) | 2024.06.20 |
job assignment problem으로 알아보는 비트마스킹을 이용한 다이나믹 프로그래밍 (0) | 2024.03.27 |
방향 비순환 그래프(DAG)위에서 다이나믹 프로그래밍 배우기 (0) | 2024.01.08 |