京都大学 情報学研究科 通信情報システム専攻 2017年8月実施 専門基礎B [B-7]
Author
祭音Myyura
Description
最大連続部分和問題とは、与えられた 個の整数 に対し、最大の連続部分和を求める問題である。 すなわち、 から までのすべての要素の和を
と記したとき、 が最大となるような整数 と (ただし、)を求める問題である。 本設問では、すべての と に対して、部分和 の絶対値が で抑えられる(すなわち、)と仮定する。 ただし、 は に依存しない定数である。以下のすべての問に答えよ。
(1) 以下の表は のときの入力の例である。 この例において が最大となるような整数 と (ただし、)を求めよ。
(2) 任意の整数 が与えられたとき、部分和 が最大となるような と を求める問題を考える。 この問題を 時間で解くアルゴリズムを与えよ。
(3) 分割統治法及び (2) のアルゴリズムを用いて、最大連続部分和問題を 時間で解くアルゴリズムを与えよ。
(4) 任意の に対し、 の最大値(ただし、)を と表す。任意の に対し、
が成立することを示せ。この関係式に基づき、最大連続部分和問題を 時間で解くアルゴリズムを与えよ。
题目描述
最大连续子数组和问题:给定 个整数 ,记
求使 最大的整数 ,其中 。假设对所有 ,都有 ,其中常数 与 无关。回答:
-
对 及下表输入,求使 最大的 :
-
给定任意 ,给出 时间算法,求使 最大的 与 。
-
利用分治法及第 2 问算法,给出 时间的最大连续子数组和算法。
-
对 ,令 。证明对 ,
并据此给出 时间的最大连续子数组和算法。
Kai
(1)
(2)
The idea is to find the maximum sum ending at and starting somewhere to its left, then the maximum sum starting at and ending somewhere to its right. Since
the two endpoints can be optimized independently, and the fixed term is added afterward.
def max_crossing_sum(A, n, k): # A is indexed from 1
current_sum = 0
max_left_sum = -float("inf")
for i in range(k - 1, 0, -1):
current_sum += A[i]
if current_sum > max_left_sum:
max_left_sum = current_sum
s = i
current_sum = 0
max_right_sum = -float("inf")
for j in range(k + 1, n + 1):
current_sum += A[j]
if current_sum > max_right_sum:
max_right_sum = current_sum
t = j
return s, t, max_left_sum + A[k] + max_right_sum
Both scans are linear, so the running time is .
(3)
The algorithm can be described as follows:
- Divide the interval at an interior midpoint .
- Recursively find the maximum subarray wholly in and wholly in .
- Use the two scans from (2) to find a maximum suffix and a maximum prefix .
- Return the best of the two recursive answers and the four possibilities containing :
Here has sum , has sum , and has sum . For an interval of length at most two, inspect all its subarrays directly.
def max_subarray(A, l, r):
if r - l + 1 <= 2:
candidates = [(l, l, A[l])]
if l < r:
candidates += [(r, r, A[r]), (l, r, A[l] + A[r])]
return max(candidates, key=lambda z: z[2])
k = (l + r) // 2
left_answer = max_subarray(A, l, k - 1)
right_answer = max_subarray(A, k + 1, r)
current_sum = 0
left_sum = -float("inf")
for i in range(k - 1, l - 1, -1):
current_sum += A[i]
if current_sum > left_sum:
left_sum, s = current_sum, i
current_sum = 0
right_sum = -float("inf")
for j in range(k + 1, r + 1):
current_sum += A[j]
if current_sum > right_sum:
right_sum, t = current_sum, j
candidates = [
left_answer,
right_answer,
(k, k, A[k]),
(s, k, left_sum + A[k]),
(k, t, A[k] + right_sum),
(s, t, left_sum + A[k] + right_sum),
]
return max(candidates, key=lambda z: z[2])
Thus
(4)
is the largest sum of a subarray ending at . Its calculation has two cases.
If the maximum sum of a subarray ending at is negative, retaining it can only decrease the sum, so the best subarray ending at starts at and has sum . If is nonnegative, appending to a subarray attaining gives the maximum. Therefore
Every nonempty subarray ends at some , so the required maximum is .
def max_subarray_sum(A, n): # A is indexed from 0
dp = [0] * n
start = [0] * n
dp[0] = A[0]
start[0] = 0
best_sum = dp[0]
best_s = best_t = 0
for i in range(1, n):
if A[i] > dp[i - 1] + A[i]:
dp[i] = A[i]
start[i] = i
else:
dp[i] = dp[i - 1] + A[i]
start[i] = start[i - 1]
if dp[i] > best_sum:
best_sum = dp[i]
best_s = start[i]
best_t = i
return best_s + 1, best_t + 1, best_sum # endpoints use the problem's 1-based indexing
The loop is executed once for each element, so the running time is .