题意 从n个数的数组中选出不相交的m段 求被选数的和的最大值
Max Sum 的升级版 不只是要选一段连续的了 而是选m段 思想还是类似 依旧dp
状态和状态转移方程不是很难想 在 Max Sum 这个问题中 dp[i] 表示的是以第i个数结尾的一段的 Max Sum 由于这里还有一个多少段的状态 于是这里令 dp[i][j] 表示在前 i 个数中选取 j 组 且第 i 个数在最后一组中的 Max Sum Plus Plus
那么现在对于第i个数有两种决策
1, 第 i 个数和第 i-1 个数连接成一段 dp[i][j] = dp[i-1][j] + a[i]
2, 第 i 个数自己单独做一段 那么前面就需要有 j-1 段 dp[i][j] = max{dp[k][j-1] | j - 1<= k < i} + a[i]
那么也就有了状态转移方程 dp[i][j] = max(dp[i-1][j], max{dp[k][j-1] | j - 1<= k < i}) + a[i];
有了方程就可以写了呀 我们先不考虑复杂度问题 假设时间空间都是无限的 恩 无限的
那么很容易有下面的代码
#include
#include
#include
using namespace std;
const int N = 10005, INF = 1e9;
int a[N], dp[N][N];
int main()
{
int n, m;
while(~scanf("%d%d", &m, &n))
{
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
memset(dp, 0, sizeof dp);
//dp[i][j] 表示前i个数第i个数被选 选j组的Max Sum Plus Plus
//dp[i][j] = max(dp[i-1][j], max{dp[k][j-1], j - 1 <= k < i}) + a[i]
int ans = -INF;
for(int j = 1; j <= m; j++)
{
for(int i = j; i <= n; i++)
{
dp[i][j] = i - 1 < j ? -INF : dp[i - 1][j]; //i-1 < j时dp[i - 1][j]是没意义的
for(int k = j - 1; k < i; ++k)
dp[i][j] = max(dp[i][j], dp[k][j - 1]);
dp[i][j] += a[i];
if(j == m) ans = max(ans, dp[i][j]);
}
}
printf("%d\n", ans);
}
return 0;
}
再来看看转移方程 dp[i][j] = max(dp[i-1][j], max{dp[k][j-1] | j - 1<= k < i}) + a[i];
发现更新 dp[i][j] 的时候只用到了 dp[.][j] 和 dp[.][j-1] 里面的值 也就是 dp[.][0~j-2] 都已经没用了 那也就不用存这些没用的了 也就是j只用开两维就够了 也就是所谓的滚动数组了 用 dp[.][1] 和 dp[.][0] 来轮换表示 dp[.][j] 和 dp[.][j-1] 这样空间复杂度就降到了O(n) 这是可以接受的 那么有空间优化后的代码
#include
#include
#include
using namespace std;
const int N = 1000005, INF = 1e9;
int a[N], dp[N][2];
int main()
{
int n, m;
while(~scanf("%d%d", &m, &n))
{
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
memset(dp, 0, sizeof dp);
//dp[i][j] 表示前i个数第i个数被选 选j组的Max Sum Plus Plus
//dp[i][j] = max(dp[i-1][j], max{dp[k][j-1], j - 1 <= k < i}) + a[i]
int ans = -INF, j0 = 0, j1 = 1;
for(int j = 1; j <= m; j++)
{
for(int i = j; i <= n; i++)
{
//dp[i][j0]存的是dp[i][j-1]的值 dp[i][j1] 存的是dp[i][j]的值
dp[i][j1] = i - 1 < j ? -INF : dp[i - 1][j1]; //i-1 < j时dp[i - 1][j]是没意义的
for(int k = j - 1; k < i; ++k)
dp[i][j1] = max(dp[i][j1], dp[k][j0]);
dp[i][j1] += a[i];
if(j == m) ans = max(ans, dp[i][j1]);
}
swap(j0, j1); //滚动数组交换0, 1 因为这轮的j在下轮就变成j - 1了
}
printf("%d\n", ans);
}
return 0;
}
现在空间问题解决了 那么时间问题呢 来看这段代码
for(int j = 1; j <= m; j++)
{
for(int i = j; i <= n; i++)
{
//
dp[i][j1] = i - 1 < j ? -INF : dp[i - 1][j1]; //
for(int k = j - 1; k < i; ++k)
dp[i][j1] = max(dp[i][j1], dp[k][j0]); //这轮循环只比上轮多了个dp[i-1][j0]!!!
dp[i][j1] += a[i];
if(j == m) ans = max(ans, dp[i][j1]);
}
swap(j0, j1); //
}
看注释部分!!! 这轮只比上轮多了一个数 我还循环那么多是什么鬼 把上轮的值 ma 记录下来 这轮只用和 dp[i-1][j0] 比较一下就行了 那么就有了可以ac的代码
#include
#include
#include
using namespace std;
const int N = 1000005, INF = 1e9;
int a[N], dp[N][2];
int main()
{
int n, m;
while(~scanf("%d%d", &m, &n))
{
for(int i = 1; i <= n; i++)
scanf("%d", &a[i]);
memset(dp, 0, sizeof dp);
//dp[i][j] 表示前i个数第i个数被选 选j组的Max Sum Plus Plus
//dp[i][j] = max(dp[i-1][j], max{dp[k][j-1], j - 1 <= k < i}) + a[i]
int ans = -INF, j0 = 0, j1 = 1;
for(int j = 1; j <= m; j++)
{
int ma = dp[j - 1][j1] = -INF; //dp[j - 1][j]是没意义的
for(int i = j; i <= n; i++)
{
//dp[i][j0]存的是dp[i][j-1]的值 dp[i][j1]存的是dp[i][j]的值
ma = max(ma, dp[i - 1][j0]); //ma维护max{dp[k][j-1], j - 1 <= k < i}
dp[i][j1] = max(dp[i - 1][j1], ma) + a[i];
if(j == m) ans = max(ans, dp[i][j1]);
}
swap(j0, j1); //滚动数组交换0, 1 因为这轮的j在下轮就变成j - 1了
}
printf("%d\n", ans);
}
return 0;
}