Problem Description
Now I think you have got an AC in Ignatius.L’s “Max Sum” problem. To be a brave ACMer, we always challenge ourselves to more difficult problems. Now you are faced with a more difficult problem.
Given a consecutive number sequence
S
1
,
S
2
,
S
3
,
S
4
.
.
.
S
x
,
.
.
.
S
n
(
1
≤
x
≤
n
≤
1
,
000
,
000
,
−
32768
≤
S
x
≤
32767
)
S_{1}, S_{2}, S_{3}, S_{4} ... S x, ... S_{n} \space (1 \le x \le n \le 1,000,000, -32768 \le S_{x} \le 32767)
S1,S2,S3,S4...Sx,...Sn (1≤x≤n≤1,000,000,−32768≤Sx≤32767).
We define a function
s
u
m
(
i
,
j
)
=
S
i
+
.
.
.
+
S
j
(
1
≤
i
≤
j
≤
n
)
sum(i, j) = S_{i} + ... + S_{j} \space (1 \le i \le j \le n)
sum(i,j)=Si+...+Sj (1≤i≤j≤n)
Now given an integer m (m > 0), your task is to find m pairs of i and j which make
s
u
m
(
i
1
,
j
1
)
+
s
u
m
(
i
2
,
j
2
)
+
s
u
m
(
i
3
,
j
3
)
+
.
.
.
+
s
u
m
(
i
m
,
j
m
)
sum(i_{1}, j_{1}) + sum(i_{2}, j_{2}) + sum(i_{3}, j_{3}) + ... + sum(i_{m}, j_{m})
sum(i1,j1)+sum(i2,j2)+sum(i3,j3)+...+sum(im,jm)
maximal (
i
x
≤
i
y
≤
j
x
o
r
i
x
≤
j
y
≤
j
x
i_{x} \le i_{y} \le j_{x} \space or \space i_{x} \le j_{y} \le j_{x}
ix≤iy≤jx or ix≤jy≤jx is not allowed).
But I’m lazy, I don’t want to write a special-judge module, so you don’t have to output m pairs of i and j, just output the maximal summation of s u m ( i x , j x ) ( 1 ≤ x ≤ m ) sum(i_{x}, j_{x}) \space (1 \le x \le m) sum(ix,jx) (1≤x≤m) instead.
Input
Each test case will begin with two integers m and n, followed by n integers
S
1
,
S
2
,
S
3
,
S
4
.
.
.
S
x
,
.
.
.
S
n
S_{1}, S_{2}, S_{3}, S_{4} ... S x, ... S_{n}
S1,S2,S3,S4...Sx,...Sn
Process to the end of file.
Output
Output the maximal summation described above in one line.
Sample Input
1 3 1 2 3
2 6 -1 4 -2 3 -2 3
Sample Output
6
8
\newline
\newline
\newline
\newline
思路
题目概括一下就是给定一个数组,要从中取出 m 个互不重叠的子数组,并且使子数组的和的总和最大。
方向是往动态规划考虑,怎么定义状态是个问题。
状态定义1
定义 d p [ i ] [ j ] dp[i][j] dp[i][j] 为数组前 i i i 个元素中取 j j j 个子数组,总和的最大值。
状态转移方程为:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
m
a
x
0
≤
k
<
i
d
p
[
k
]
[
j
−
1
]
+
s
u
m
(
i
−
1
)
−
s
u
m
(
k
−
1
)
)
dp[i][j] = max(dp[i - 1][j], max_{0 \le k < i} \space dp[k][j - 1] + sum(i - 1) - sum(k - 1))
dp[i][j]=max(dp[i−1][j],max0≤k<i dp[k][j−1]+sum(i−1)−sum(k−1))
\newline
其中
s
u
m
(
i
)
sum(i)
sum(i) 为前缀和,我们可以用一个变量维护前缀
m
a
x
0
≤
k
<
i
(
d
p
[
k
]
[
j
−
1
]
−
s
u
m
(
k
−
1
)
)
max_{0 \le k < i} \space (dp[k][j - 1] - sum(k - 1))
max0≤k<i (dp[k][j−1]−sum(k−1)) 最大值。
另外可以用滚动数组来减少一维空间使用量。算法时间复杂度为 O ( m n ) O(mn) O(mn),空间使用为 O ( n ) O(n) O(n)。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private long solve(long[] arr, int m) {
int n = arr.length;
long[] prev = new long[n + 1];
long[] sum = new long[n];
for (int i = 0; i < n; ++i) {
sum[i] = arr[i] + (i > 0 ? sum[i - 1] : 0);
}
for (int i = 1; i <= m; ++i) {
long[] next = new long[1 + n];
long max = prev[i - 1] - (i > 1 ? sum[i - 2] : 0);
/**
* The next[] array value is only meaningful when j >= i.
* The prev[] array value is only meaningful when j >= i - 1.
*/
for (int j = i; j <= n; ++j) {
next[j] = max + sum[j - 1];
// Only when j > i then next[j - 1] is meaningful.
if (j > i) {
next[j] = Math.max(next[j], next[j - 1]);
}
max = Math.max(max, prev[j] - sum[j - 1]);
}
prev = next;
}
return prev[n];
}
public static void main(String[] args) {
Main main = new Main();
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int m = sc.nextInt();
int n = sc.nextInt();
long[] arr = new long[n];
for (int i = 0; i < n; ++i) {
arr[i] = sc.nextLong();
}
System.out.println(main.solve(arr, m));
}
}
}
\newline
\newline
\newline
\newline
状态定义2
前一个方法需要用一个变量维护前缀 m a x 0 ≤ k < i ( d p [ k ] [ j − 1 ] − s u m ( k − 1 ) ) max_{0 \le k < i} \space (dp[k][j - 1] - sum(k - 1)) max0≤k<i (dp[k][j−1]−sum(k−1)) 最大值,看上去就比较复杂,很 tricky 而且易错。造出这种情况的原因是状态定义的不够好,试试在前一个方法的状态定力里面附加一个额外条件:必须以 A [ i − 1 ] A[i - 1] A[i−1] 结尾。
定义状态
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j] 为从数组的前
i
i
i 个元素中取
j
j
j 个子数组,且最后一个子数组以元素
A
[
i
−
1
]
A[i - 1]
A[i−1] 结尾。这样最后的结果为
m
a
x
(
d
p
[
m
]
[
0
]
,
d
p
[
m
]
[
1
]
,
.
.
.
d
p
[
m
]
[
n
−
1
]
)
max(dp[m][0], dp[m][1], ... dp[m][n - 1])
max(dp[m][0],dp[m][1],...dp[m][n−1])
状态转移方程为:
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
m
a
x
0
≤
k
<
i
d
p
[
k
]
[
j
−
1
]
)
+
A
[
i
−
1
]
dp[i][j] = max(dp[i - 1][j], \space max_{0 \le k < i} \space dp[k][j - 1] ) + A[i - 1]
dp[i][j]=max(dp[i−1][j], max0≤k<i dp[k][j−1])+A[i−1]
这里 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 是指元素 A [ i − 1 ] A[i - 1] A[i−1] 和前面一个元素合并属于组同一个子数组的情况。 m a x 0 ≤ k < i d p [ k ] [ j − 1 ] + A [ i − 1 ] max_{0 \le k < i} \space dp[k][j - 1] + A[i - 1] max0≤k<i dp[k][j−1]+A[i−1] 是元素 A [ i − 1 ] A[i - 1] A[i−1] 另起炉灶作为一个新的子数组。这样我们只需维护一个前缀 m a x 0 ≤ k < i d p [ k ] [ j − 1 ] max_{0 \le k < i} \space dp[k][j - 1] max0≤k<i dp[k][j−1] 的最大值,比上一个方法简单很多。
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private long solve(long[] arr, int m) {
int n = arr.length;
long[] prev = new long[n + 1];
for (int i = 1; i <= m; ++i) {
long[] next = new long[n + 1];
long max = prev[i - 1];
for (int j = i; j <= n; ++j) {
next[j] = max + arr[j - 1];
if (j > i) {
next[j] = Math.max(next[j], arr[j - 1] + next[j - 1]);
}
max = Math.max(max, prev[j]);
}
prev = next;
}
long ans = Long.MIN_VALUE;
// array is only meaningful from i >= m.
for (int i = m; i <= n; ++i) {
ans = Math.max(ans, prev[i]);
}
return ans;
}
public static void main(String[] args) {
Main main = new Main();
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
int m = sc.nextInt();
int n = sc.nextInt();
long[] arr = new long[n];
for (int i = 0; i < n; ++i) {
arr[i] = sc.nextLong();
}
System.out.println(main.solve(arr, m));
}
}
}