题意
n个数取m个不重合的部分使和最大,求这个最大值。
思路
枚举比大小的思路肯定不可取,尝试动态规划。
动态规划最自然的想法是:求n个数取若干部分和的最大值,子问题就是对n-1个数取若干部分和的最大值。
设a[n]为n个数的序列,dpp[n][m]为n个数取m部分的最大值,可以有两种决策达到dpp[n][m]的状态:
- 第n个数构成第m部分
- 第n个数不构成第m部分
所以状态转移方程,
1
≤
x
≤
n
−
m
+
1
1\leq x\leq n-m+1
1≤x≤n−m+1:
d
p
p
[
n
]
[
m
]
=
m
a
x
(
m
a
x
(
d
p
p
[
n
−
x
]
[
m
−
1
]
+
∑
i
=
n
−
x
+
1
n
a
[
i
]
)
,
d
p
p
[
n
−
1
]
[
m
]
)
dpp[n][m]=max(max(dpp[n-x][m-1]+\sum_{i=n-x+1}^n a[i]), dpp[n-1][m])
dpp[n][m]=max(max(dpp[n−x][m−1]+i=n−x+1∑na[i]),dpp[n−1][m])
对于这个方程我们考虑实现,n需要一层遍历,m需要一层遍历,x需要一层遍历,变成的
O
(
n
3
)
O(n^3)
O(n3)的算法,应该会超时。
尝试优化第一个决策引入的x,尝试消除x的影响。
设dp[n][m]为n个数取m部分的最大值,且第n个数构成第m部分,之所以这样设是因为我观察到
m
a
x
(
d
p
p
[
n
−
x
]
[
m
−
1
]
+
∑
i
=
n
−
x
+
1
n
a
[
i
]
)
max(dpp[n-x][m-1]+\sum_{i=n-x+1}^n a[i])
max(dpp[n−x][m−1]+∑i=n−x+1na[i])中有这样(dp[n][m])的部分。(有点类似最长上升子序列的状态)
d
p
[
n
]
[
m
]
=
m
a
x
(
d
p
p
[
n
−
x
]
[
m
−
1
]
+
∑
i
=
n
−
x
+
1
n
a
[
i
]
)
dp[n][m]=max(dpp[n-x][m-1]+\sum_{i=n-x+1}^n a[i])
dp[n][m]=max(dpp[n−x][m−1]+i=n−x+1∑na[i])
=
m
a
x
(
d
p
p
[
n
−
x
]
[
m
−
1
]
+
∑
i
=
n
−
x
+
1
n
−
1
a
[
i
]
)
+
a
[
n
]
=max(dpp[n-x][m-1]+\sum_{i=n-x+1}^{n-1} a[i])+a[n]
=max(dpp[n−x][m−1]+i=n−x+1∑n−1a[i])+a[n]
=
m
a
x
(
d
p
p
[
n
−
1
]
[
m
−
1
]
,
d
p
[
n
−
1
]
[
m
]
)
+
a
[
n
]
=max(dpp[n-1][m-1], dp[n-1][m])+a[n]
=max(dpp[n−1][m−1],dp[n−1][m])+a[n]
这样消除了x的影响,变成了
O
(
n
2
)
O(n^2)
O(n2)的算法。回过头在看最后公式的两部分可以看成:
- 第n个数独立构成第m部分的情况
- 第n个数和前面的数一同构成第m部分的情况
同时
d
p
p
[
n
]
[
m
]
=
m
a
x
(
d
p
[
n
]
[
m
]
,
d
p
p
[
n
−
1
]
[
m
]
)
dpp[n][m]=max(dp[n][m], dpp[n-1][m])
dpp[n][m]=max(dp[n][m],dpp[n−1][m])
由于状态转移方程只涉及相邻项的关系,所以可以状态压缩。(注意顺序)
这题动态规划的边界条件是试出来的,详情看代码。
看到航电discuss的另一中写法,把分成m部分看成阶段。
代码
#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;
typedef long long LL;
const LL INF = -1e18;
int s[1000010];
LL dp[1000010];
LL dpp[1000010];
int main(){
int m, n;
while(~scanf("%d%d", &m, &n)){
dpp[0] = dp[0] = 0;
for(int i = 1; i <= m; ++i){
dp[i] = INF;
dpp[i] = INF;
}
for(int i = 1; i <= n; ++i){
scanf("%d", s + i);
for(int j = min(i, m); j > 0; --j){
dp[j] = max(dpp[j-1], dp[j]) + s[i];
dpp[j] = max(dpp[j], dp[j]);
}
}
printf("%I64d\n", dpp[m]);
}
return 0;
}
总结
- 第一次感觉数学是一个工具,并且是一个好工具。