#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e6+100; const int INF = 0x3f3f3f3f; int a[1000], n, m; /* 方法一; 枚举每个子段和; {a0},{a0, a1},{a0, a1, ..., an-1}; {a1},{a1, a2},{a1, a2, ..., an-1}; ...; {an-1}; 时间复杂度:O(n^3); */ int solve_1(){ int ans=0, sum=0; for(int i=1; i<=n; i++){ for(int j=i; j<=n; j++){ sum=0; for(int k=i; k<=j; k++){ sum+=a[k]; } ans=max(ans, sum); } } return ans; } /* 方法二; 和一的思想一样, 优化一下; 时间复杂度:O(n^2); */ int solve_2(){ int ans=0, sum=0; for(int i=1; i<=n; i++){ sum=0; for(int j=i; j<=n; j++){ sum+=a[j]; ans=max(ans, sum); } } return ans; } /* 方法三: 分治; 最大子段有可能在左侧, 也可能在右侧, 也可能是左右合并; 时间复杂度:O(nlogn); */ int solve_3(int left, int right){ int leftmax, rightmax; if(left==right) return a[left]>0?a[left]:0; int mid=(left+right)/2; leftmax=solve_3(left, mid); rightmax=solve_3(mid+1, right); int sum, leftsum, rightsum; sum=leftsum=0; for(int i=mid; i>=left; i--){ sum+=a[i]; if(sum>leftsum) leftsum=sum; } sum=rightsum=0; for(int i=mid+1; i<=right; i++){ sum+=a[i]; if(sum>rightsum) rightsum=sum; } int retsum=leftsum+rightsum; return max(retsum, max(leftmax, rightmax)); } /* 方法四: 动规; 只要前边和不为负数, 那么加上后边一个正数, 结果有可能会变大; 时间复杂度:O(n); */ int dp[1000]; int solve_4(){ dp[0]=0; int ans=dp[0]; for(int i=1; i<=n; i++){ if(dp[i-1]+a[i]<=0) dp[i]=a[i]; else dp[i]=dp[i-1]+a[i]; ans=max(dp[i], ans); } return ans; } /* 将最大子段和问题升级为最大m子段和; 即将数列分为m段不相交子段, 求最大和; */ /* 一: 定义二维数组dp[i][j]表示前j个数分成i组的最大和; dp[i][j]=max(dp[i][j-1](第j个和前边的一起划分到第i组), max(dp[i-1][i-1~j-1](j单独作为第i组)))+a[j]; 时间复杂度: O(mn^2) */ int DP[1000][1000]; int solve_m_1(){ int ans=-INF; memset(DP, 0, sizeof(DP)); for(int i=1; i<=m; i++){ DP[i][i]=DP[i-1][i-1]+a[i]; for(int j=i+1; j<n; j++){ int temp=-INF; for(int k=i-1; k<=j-1; k++){ temp=max(temp, DP[i-1][k]); } DP[i][j]=max(DP[i][j-1], temp)+a[j]; } } for(int i=m; i<=n; i++) ans=max(ans, DP[m][i]); return ans; } /* 上个方法时间复杂度有点大, 能不能再降低呢? 可以!max(dp[i-1][i-1~j-1])的计算方法其实是可以省去一层循环的; 而且在空间复杂度上也可以降到一维; */ int solve_m_2(){ int ans=-INF; int temp[1000]; memset(temp, 0, sizeof(temp)); memset(dp, 0, sizeof(dp)); for(int i=1; i<=m; i++){ ans=-INF; for(int j=i; j<=n; j++){ dp[j]=max(dp[j-1], temp[j-1])+a[j]; temp[j-1]=ans; ans=max(ans, dp[j]); } } return ans; } int main(){ scanf("%d%d", &n, &m); for(int i=1; i<=n; i++){ scanf("%d", &a[i]); } printf("1:%d\n2:%d\n3:%d\n4:%d\nm_1:%d\nm_2:%d\n", solve_1(), solve_2(), solve_3(1, n), solve_4(), solve_m_1(), solve_m_2()); return 0; }
下面附上两道练习题:
HDU 1024 Max Sum Plus Plus
这道题用一维数组做, 二维空间复杂度太大, 数组开不开;
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e6+100; const int INF = 0x3f3f3f3f; int dp[maxn], s[maxn], temp[maxn]; int main(){ int n, m; while(~scanf("%d%d", &m, &n)){ for(int i=1; i<=n; i++){ scanf("%d", &s[i]); } memset(dp, 0, sizeof(dp)); memset(temp, 0, sizeof(temp)); int ans=-INF; for(int i=1; i<=m; i++){ ans=-INF; for(int j=i; j<=n; j++){ dp[j]=max(dp[j-1]+s[j], temp[j-1]+s[j]); temp[j-1]=ans; ans=max(ans, dp[j]); } } printf("%d\n", ans); } return 0; }
最大M子段和
这道题要求输出整数解, 需要特判一下, 如果分段数大于正整数个数, 输出所有正整数的和;
#include <cstdio> #include <algorithm> #include <cstring> using namespace std; const long long INF = 0x3f3f3f3f; long long a[5100]; long long dp[5100], temp[5100]; int main(){ int N, M, cnt=0; scanf("%d%d", &N, &M); for(int i=1; i<=N; i++){ scanf("%lld", &a[i]); if(a[i]>0) cnt++; } if(cnt<=M){//特判; long long ans=0; for(int i=1; i<=N; i++) ans+=(a[i]>=0?a[i]:0); printf("%lld\n", ans); return 0; } memset(dp, 0, sizeof(dp)); memset(temp, 0, sizeof(dp)); long long ans; for(int i=1; i<=M; i++){ ans=-INF; for(int j=i; j<=N; j++){ dp[j]=max(dp[j-1], temp[j-1])+a[j]; temp[j-1]=ans; ans=max(ans, dp[j]); } } printf("%lld\n", ans); return 0; }