思路:
- M个子段的最大和。
- 动态规划拢共分三步:
- 递推突破口:子段和类问题都是选新数字与不选新数字,选新数字又分为吸附与拓展。
- 状态转移方程:先用正方形 dp 找出原始方程,然后根据条件(本题中是左段右段)找到变量边界,并且必不可少的是:方程中一定有端格是超限的,这时要手动解决。
- 优化:空间开销一般可以缩小到很少的维数。
代码:
int sum[maxn];
int L_dp[maxn][maxn];
int dp[maxn][maxn];
for(int i=1;i<=M;i++){
for(int j=i;j<=N-M+i;j++){
if(i == j)
L_dp[i][j] = dp[i][j] = sum[j];
else{
int w = sum[j] - sum[j-1] ;
L_dp[i][j] = max(dp[i-1][j-1] , L_dp[i][j-1]) + w;
dp[i][j] = max(dp[i][j-1] , L_dp[i][j]) ;
}
}
}
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#define INF 0x3f3f3f3f
using namespace std;
const int maxn = 1e6 + 5;
int N,M;
int sum[maxn];
int dp[2][maxn];
int L_dp[maxn];
void INIT(){
memset(dp , 0 , sizeof(dp));
memset(L_dp , 0 , sizeof(L_dp));
memset(sum , 0 , sizeof(sum));
return ;
}
int main(){
while(~scanf("%d%d" , &M , &N)){
INIT();
for(int i=1;i<=N;i++){
int w;
scanf("%d" , &w);
sum[i] = sum[i-1] + w;
}
int t = 1;
for(int i=1;i<=M;i++){
t = 1 - t;
for(int j=i;j<=N-M+i;j++){
if(i == j)
L_dp[j] = dp[t][j] = sum[j];
else{
int w = sum[j] - sum[j-1];
L_dp[j] = max(dp[1-t][j-1], L_dp[j-1]) + w;
dp[t][j] = max(dp[t][j-1] , L_dp[j]) ;
}
}
}
printf("%d\n" , dp[1-M%2][N]);
}
return 0;
}
二刷:
- 做了很久,但是第一次找到了做DP题的感觉。
- 而且发现,这题还真是蛮好的。一步一步重新构建方法都必须很有逻辑才行。
- L_dp [ j ] 代表用 j 个数而且必须用第 j 个数获得的上一层段数的最大值。
- 499ms 17020kB
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn = 1e6 + 5;
int N,M;
int sum[maxn];
int dp[2][maxn];
int L_dp[maxn];
int main(){
while(~scanf("%d%d" , &M , &N)){
memset(sum , 0 , sizeof(sum));
memset(dp , 0 , sizeof(dp));
memset(L_dp , 0 , sizeof(L_dp));
for(int i=1;i<=N;i++){
int k;
scanf("%d" , &k);
sum[i] = sum[i-1] + k;
}
int t = 1;
for(int i=1;i<=M;i++){
for(int j=i;j<=N-M+i;j++)
if(i == j)
L_dp[j] = dp[t][j] = sum[j];
else{
int k = sum[j] - sum[j-1];
dp[t][j] = max(max(dp[t][j-1] , dp[1-t][j-1] + k) , L_dp[j-1] + k);
L_dp[j] = max(dp[1-t][j-1] , L_dp[j-1]) + k;
}
t = 1-t;
}
printf("%d\n" , dp[M%2][N]);
}
return 0;
}