关键字:dp 滚动数组
题意:输入n,m表示 一排n个数,求m个不相交的子段,他们和最大。
思路:果断dp啊,最大子段和的变形,联想最大子段和的dp[i] 设以当前i为结尾的子段和最大的结果,这里设dp[i][j]为以j结尾构成了i段不相交子段和的最大结果。
我们就要思考当前dp[i][j]是由那些状态转移过来?思考这里对于每个数字,我们的策略是要么选择他作为当前这段的,要么让它单独成段(对于这一段不一定要选),单独成一段的话,就又要从上一层的状态里dp[i-1][k],(表示选择第k个数字后已经形成I-1 个段落,i-1<=k<=j-1)里选择最优的那一个.
所以分析后,他的状态转移方程是 dp[i][j]=max(dp[i][j-1]+a[j],max(dp[i-1][k])+a[j]);
这里有一个小技巧,对于这个状态转移方程式,很多菜鸟推出来就容易写成O(n^3)
对于查找dp[i-1][k] 中最大的那一个,我么可以用一个一维数组Max[j]保存上一轮选择到第j个数字前的最大结果,这样时间复杂度O(n*m)。
但是这题j的范围是10^6,i未知,时间肯定不会超的,因为想不到更优的转移方程。然而空间需要优化一点,利用转移方程式只与i.i+1有关,就用滚动数组,节省一维空间。
#include<bits/stdc++.h> using namespace std; const int maxn=1000010; const int inf=0x7fffffff; int dp[maxn],a[maxn],Max[maxn],res; 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)); memset(Max,0,sizeof(Max)); for(int i=1;i<=m;i++){ res=-inf; for(int j=i;j<=n;j++){ dp[j]=max(dp[j-1],Max[j-1])+a[j]; Max[j-1]=res; res=max(res,dp[j]); } } printf("%d\n",res); } }