最大子段和问题及其推广
实在是太颓废了,所以开始写点东西吧。。。。
最大子段和: 给定N个数(可能为负整数)组成的序列a1...an,求该序列形如∑ak(i<=k<=j)的子段和的最大值。当所有整数均为负整数时定义其最大的子段和为0.
依据题意我们可以确定 最优值为max(0,max∑ak(i<=i<=j<=n)); 若我们令b[j] = max∑ak(1<=i<=j)是从序列a的第i个数到第j个数的最大和,那么我们最后的答案就是找出b数组里面记录的最大的那个值就是答案。由b[j]的定义可以知道b[j-1]>0时,b[j] = b[j-1]+a[j]否则b[j] = a[j];由此得出递推式b[j] = max(b[j-1]+a[j],a[j]) (1<=j<=n)
代码如下
1 int Maxsum1(int n,int *a) 2 { 3 int sum = 0,b = 0; 4 for(int i=1;i<=n;i++){ 5 if(b>0) b += a[i]; 6 else b = a[i]; 7 if(b > sum ) sum = b; 8 } 9 return sum; 10 }
最大子段和的推广
(1)最大子矩阵:给定一个m行n列的整数矩阵A,求其元素和最大的子矩阵
很明显最大子矩阵和的问题是最大子段和的问题向二维的推广,我们用数组a[1:m][1:n]表示给定的矩阵A 其子数组a[i1:i2][j1:j2]表示左上角和右下角的行列坐标分别为(i1,j1)(i2,j2)的子矩阵 其个各个元素之和 记为 S(i1,i2,j1,j2)= ∑∑a[i][j] (i1<=i<=i2,j1<=j<=j2) 那么ans = Max S(i1,i2,j1,j2) (1<=i1<=i2<=m,1<=j1<=j2<=n)
我们可以令t(i1,i2) = maxS(i1,i2,j1,j2)(i<=j1<=j2<=n) = Max∑∑a[i][j] 同样我们令b[j] = ∑a[i][j](i1<=i<=i2) 于是 t(i1,i2) = max∑b[j](1<=j1<=j2<=n) 这又回到了一维的情况于是我们可以得到如下代码
1 int Maxsum2(int m,int n,int a[105][105] ) 2 { 3 int sum = 0; 4 int b[105]; 5 for(int i=1;i<=m;i++){ 6 memset(b,0,sizeof(b)); 7 for(int j=i;j<=m;j++){ 8 for(int k=1;k<=n;k++) b[k] += a[j][k]; 9 int Max = Maxsum1(n,b); 10 if(Max > sum) sum = Max; 11 } 12 } 13 return sum ; 14 }
最大子矩阵在poj上面有原题目 题号是1050 代码如下
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int M = 105; int a[M][M]; int Maxsum1(int n,int *a) { int sum = 0,b = 0; for(int i=1;i<=n;i++){ if(b>0) b += a[i]; else b = a[i]; if(b > sum ) sum = b; } return sum; } int Maxsum2(int m,int n,int a[105][105] ) { int sum = 0; int b[105]; for(int i=1;i<=m;i++){ memset(b,0,sizeof(b)); for(int j=i;j<=m;j++){ for(int k=1;k<=n;k++) b[k] += a[j][k]; int Max = Maxsum1(n,b); if(Max > sum) sum = Max; } } return sum ; } int main() { int n; scanf("%d",&n); for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++) scanf("%d",&a[i][j]); } printf("%d\n",Maxsum2(n,n,a)); return 0; }
2 最大M子段和
最大子段和是最大M子段和的特殊情况即M = 1时的解,我们用b(i,j)表示数组a的前j项中i个子段和的最大值 那么很容易得到如下状态转移方程 b(i,j) = max(b(i,j-1),max(b(i-1,t))+a[j] 第一个状态表示第i个子段包含a[j],而第二个状态表示第i个子段只包含a[j] 于是很容易得到如下代码
1 int MaxSum() 2 { 3 memset(b,0,sizeof(b)); 4 for(int i=1;i<=m;i++){ 5 for(int j=i;j<=n-m+i;j++){ 6 if(j>i){ 7 b[i][j] = b[i][j-1]+a[j]; 8 for(int k=i-1;k<j;k++){ 9 b[i][j] = max(b[i][j],b[i-1][k]+a[j]); 10 } 11 12 } 13 else 14 b[i][j] = b[i-1][j-1] + a[j]; 15 16 } 17 } 18 int sum = 0; 19 for(int j=m;j<=n;j++){ 20 if(sum < b[m][j]) sum = b[m][j]; 21 } 22 return sum ; 23 }
上述算法显然需要O(mn^2)但是我们注意到 对b[i][j] 只用到了 第i-1行和第i行因此只要记录这两个值就可以,另一方面max(b(i-1,t))的值可以在计算第i-1行是预先计算出来
于是得到优化后的代码如下
int dp() { b[0] = 0; c[1] = 0; for(int i=1;i<=m;i++){ b[i] = b[i-1] + a[i]; c[i-1] = b[i]; int tmp = b[i]; for(int j=i+1;j<=n-m+i;j++){ b[j] = max(b[j-1],c[j-1])+a[j]; c[j-1] = tmp; if(tmp < b[j]) tmp = b[j]; } c[i+n-m] = tmp; } printf("%d\n",c[n]); }
最大M子段和问题在杭电OJ上有原题 题号是1024
AC代码如下
#include <cstdio> #include <iostream> #include <cstring> using namespace std; const int M = 1000005; int n,m; int a[M]; int b[M],c[M]; int dp() { b[0] = 0; c[1] = 0; for(int i=1;i<=m;i++){ b[i] = b[i-1] + a[i]; c[i-1] = b[i]; int tmp = b[i]; for(int j=i+1;j<=n-m+i;j++){ b[j] = max(b[j-1],c[j-1])+a[j]; c[j-1] = tmp; if(tmp < b[j]) tmp = b[j]; } c[i+n-m] = tmp; } printf("%d\n",c[n]); } int main () { while(scanf("%d%d",&m,&n)!=EOF){ for(int i=1;i<=n;i++) scanf("%d",&a[i]); dp(); memset(c,0,sizeof(c)); } return 0; }