题目大意:给你一个长度为n的序列,让你求出把这个序列分为m个不相交子段的最大和。
题解:从今天开始重新刷一起没写完的dp了,开始自闭之旅。
首先说一下状态:
dp[i][j]表示在前j个数中分成i个子段的最大值,并且最大值以第j项结尾
状态转移方程:
dp[i][j]=max(dp[i][j-1]+a[j],dp[i-1][k]+a[j]) (0<=k<=j)
dp[i][j-1]+a[j] 表示在前j-1个数中分成i个字段的最大值,最大值以第j-1项结尾,
那就我们就可以在最后一组后面再加上一个a[j]。
dp[i-1][k]+a[j] 表示在前k个数中分成i-1个字段的最大值,a[j]单独成为第i组
状态转移方程不是特别难想,不过要清楚的明白这个递推的过程,这样下面优化的时候才能看懂,这也是关键。
不难发现我们这样写的时间复杂度是大致为O(n^3),当n和m同阶时。
下面我们考虑怎么优化,在递推时我们可以发现,每一项都是由它的左上方的最大值以及左边的值来确定,那么我们就可以每一存一下前缀的最大值,方便下一行来用,其实就是再开一个MAX数组来记录上一行的前缀最大值。
其实还有更优化的方法,不过就涉及到一些贪心的处理,可以参考51nod的最大子段和V
如果需要比较详细的分析的话可以参考这篇文章:最大字段和
trick:这个题O(nm)是可以过的,而且最后答案不是dp[m][n],想想状态的定义就知道为什么了。
代码实现:
#pragma GCC optimize(2) #include <bits/stdc++.h> using namespace std; const int N = 1e6 + 7; long long a[N]; long long dp[N], MAX[N], MAXX; /* dp[i][j]表示在前j个数中分成i个子段的最大值,并且最大值以a[j]结尾 dp[i][j]=max(dp[i][j-1]+a[j],dp[i-1][k]+a[j]) (0<=k<=j) dp[i][j-1]+a[j] 表示在前j-1个数中分成i个字段的最大值,最大值以a[j-1]结尾, 那么就我们在可以最后一组后面加上一个a[j]。 dp[i-1][k]+a[j] 表示在前k个数中分成i-1个字段的最大值,a[j]单独成为第i组 */ int main() { int n, m; while (~scanf("%d%d", &m, &n)) { for (int i = 1; i <= n; i++) MAX[i] = dp[i] = 0; int cnt = 0; for (int i = 1; i <= n; i++) scanf("%lld", &a[i]); for (int i = 1; i <= m; i++) { MAXX = -0x3f3f3f3f3f3f; for (int j = i; j <= n; j++) { dp[j] = max(dp[j - 1] + a[j], MAX[j - 1] + a[j]); MAX[j - 1] = MAXX; MAXX = max(MAXX, dp[j]); } } printf("%lld\n", MAXX); } return 0; }
dp训练计划——hdu1024最大子段和
最新推荐文章于 2024-07-21 14:30:00 发布