kuangbin专题链接:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=68966#problem/A
题意:最大m子段问题:求给定无交叉的区间数目,求出最大和值。
看到,n达到1000000,心里第一想法就是只能是一维Dp问题,可是,在推导的状态转移公式时,还得用二维Dp思想。
看到网上的算法时间复杂度为O(mn),看来m不是很大。
其实,我们很容易想到一个状态转移方程:dp[i][j]:表示在第i个位置处,区间数为j的最大和值。
则有两种转移情况:
一:dp[i][j]中的第i个位置,不属于dp[i][j]中的最后一个子段j(值等于子段数目),则 dp[i][j]=dp[i-1][j];
二:dp[i][j]中的第i个位置属于dp[i][j]中的最后一个子段j(值等于子段数目),此时,我们又容易想到:最后一个子段的终点一定,枚举最后一个子段的起点即可,状态转移方程为:dp[i][j]=max(dp[k][j-1]+s[i]-s[k]),其中s表示前缀和,1<k<i。刚开始我就是这样想的,无奈做不出来。看题解:此时,又把最后一个位置j分成两种情况:
1:dp[i][j]的最后一个位置i属于dp[i-1][j]的最后一个子段j,即:相当于把位置i放入dp[i-1][j]的尾部,则:dp[i][j]=dp[i-1][j]+a[i]。
2:dp[i][j]的最后一个位置i不属于dp[i-1][j]的最后一个子段j,即:最后一个位置属于一个独立的子段,则:dp[i][j]=dp[k][j-1]+a[i]。1<=k<i;
至此转移方程就出来了,但是n是在太大了,开一个二位数组不太妥。说要用滚动数组,其实我对滚动数组还是没怎么会用,参考别人的代码,然后写了一遍。
//freopen("C:\\Documents and Settings\\All Users\\桌面\\in.txt","r",stdin);
#include<iostream>
#include<cstdio>
#include<sstream>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<set>
#include<queue>
#include<vector>
#include<map>
#include<string>
#define LL __int64
#define INF 0x7fffffff
using namespace std;
int dp[1000010],p[1000010],a[1000010],m,n;
int main(){
while(cin>>m)
{
cin>>n;
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
memset(dp,0,sizeof(dp)),memset(p,0,sizeof(p));
int Max;
for(int i=1;i<=m;i++){
Max=-INF;
for(int j=i;j<=n;j++)//很显然,j必须>=i
//滚动数组就是及时回收利用后面用不到的空间,这里是p数组
dp[j]=max(dp[j-1],p[j-1])+a[j],p[j-1]=Max,Max=max(dp[j],Max);
}
cout<<Max<<endl;
}
return 0;
}