【题意】求最大M子段和
【分析】dp[i][j]表示前j项中i个子段和的最大值,且第i个子段包含a[j]
(a[j]可能包含在第i子段;也可能第i子段只有a[j]一个元素,那么只有把a[j]加到每一个dp[i-1][k]后面找最大值)
所以状态转移方程:dp[i][j] = max(dp[i][j-1], dp[i-1][k])(i-1 <= k <= j-1)。
可以写出:
for (int i = 1; i <= m; i++)
{
for (int j = 1; j <= n; j++)
{
dp[i][j] = dp[i][j-1];
for (int k = i-1; k < j; k++)
dp[i][j] = max(dp[i][j], dp[i-1][k]);
dp[i][j] += a[j];
}
}
但是这样复杂度m*n^2肯定超时,于是要优化。
这里发现for (int k = i-1; k < j; k++) dp[i][j] = max(dp[i][j], dp[i-1][k]);这个算max{dp[i-1][k]}在前面已经算出来了,只要保存下来就直接可以花费O(1)调用了;所以可以用另外一个数组last[]来分别保存上一次每个i-1~k的最大值max{dp[i-1][]},在用一个变量maxn来辅助计算当前到j时的最大值。这样就可以写成如下O(N*M)算法了:
for (int i = 1; i <= m; i++)
{
maxn = -INF;
for (int j = i; j <= n; j++)
{
dp[i][j] = max(dp[i][j-1], last[j-1])+a[j];
last[j-1] = maxn;
maxn = max(maxn,dp[i][j]);
}
}
又发现dp[i][j]的第一维没有用,利用滚动数组思想可以把第一维直接去掉,最终变成如下AC代码:
【AC代码】104ms(NYOJ) 640ms(HDU)
#include <cstdio>
#include <cstring>
#define MAXN 1000010
#define INF 0x3f3f3f3f
#define max(a,b) (a>b?a:b)
int ch, ans, f, a[MAXN], dp[MAXN], last[MAXN];
int in()
{
if((ch = getchar()) == '-') f = -1;
else f = 1;
while(ch < '0' || '9' < ch) ch = getchar();
ans = ch-'0';
while((ch = getchar()) >= '0' && '9' >= ch) ans = ans*10+ch-'0';
return ans*f;
}
int main()
{
#ifdef SHY
freopen("e:\\1.txt","r",stdin);
#endif
int t;
t = in();
while(t--)
{
int m = in(), n = in(), maxn;
for (int i = 1; i <= n; i++)
a[i] = in();
memset(dp,-0x3f,sizeof(dp));
memset(last,0,sizeof(last));
for (int i = 1; i <= m; i++)
{
maxn = -INF;
for (int j = i; j <= n; j++)
{
dp[j] = max(dp[j-1], last[j-1])+a[j];
last[j-1] = maxn;
maxn = max(maxn,dp[j]);
}
}
printf("%d\n", maxn);
}
return 0;
}