To the Max
问题来源:hdu-1050
Description
Given a two-dimensional array of positive and negative integers, a sub-rectangle is any contiguous sub-array of size 1*1 or greater located within the whole array. The sum of a rectangle is the sum of all the elements in that rectangle. In this problem the sub-rectangle with the largest sum is referred to as the maximal sub-rectangle.
As an example, the maximal sub-rectangle of the array:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
is in the lower left corner:
9 2
-4 1
-1 8
and has a sum of 15.
Input
The input consists of an N * N array of integers. The input begins with a single positive integer N on a line by itself, indicating the size of the square two-dimensional array. This is followed by N^2 integers separated by whitespace (spaces and newlines). These are the N^2 integers of the array, presented in row-major order. That is, all numbers in the first row, left to right, then all numbers in the second row, left to right, etc. N may be as large as 100. The numbers in the array will be in the range [-127,127].
Output
Output the sum of the maximal sub-rectangle.
Sample Input
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
Sample Output
15
源代码一(普通DP):
代码分析:从上面的这段代码我们能很明显地看出时间复杂度很高,主函数里for嵌套4层,并且求和的阶段s[m]+=map[k][m]是最频繁的一句代码,求和部分很频繁,为了节约时间,我改成了二维的树状数组来实现求和,因为树状数组求和的复杂度时O(logn)的,比起O(n)的普通求和节约了不少时间
源代码二(二维树状数组求和):
代码分析:由代码对比,很明显地看出树状数组的下标需要从1开始,从0开始是实现不了的(至少说不好实现),树状数组将求和的复杂度从O(n)降为了O(logn),但是得多开辅助存储空间C数组,这也就是经常遇到的消耗空间来节省时间(动态规划从一定意义上说就是部分深度优先搜索的节省消耗空间来节省时间的优化),对树状数组的发明者表示衷心地佩服!
问题来源:hdu-1050
Description
Given a two-dimensional array of positive and negative integers, a sub-rectangle is any contiguous sub-array of size 1*1 or greater located within the whole array. The sum of a rectangle is the sum of all the elements in that rectangle. In this problem the sub-rectangle with the largest sum is referred to as the maximal sub-rectangle.
As an example, the maximal sub-rectangle of the array:
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
is in the lower left corner:
9 2
-4 1
-1 8
and has a sum of 15.
Input
The input consists of an N * N array of integers. The input begins with a single positive integer N on a line by itself, indicating the size of the square two-dimensional array. This is followed by N^2 integers separated by whitespace (spaces and newlines). These are the N^2 integers of the array, presented in row-major order. That is, all numbers in the first row, left to right, then all numbers in the second row, left to right, etc. N may be as large as 100. The numbers in the array will be in the range [-127,127].
Output
Output the sum of the maximal sub-rectangle.
Sample Input
4
0 -2 -7 0
9 2 -6 2
-4 1 -4 1
-1 8 0 -2
Sample Output
15
源代码一(普通DP):
#include<iostream>
using namespace std;
const int MAX = 100;
int map[MAX][MAX];
int s[MAX] , num , N;
int dp( void );
int main( ){
int ans , max;
while( cin>>N ){
max = 0;
//数组下表从0开始
for( int i=0 ; i<N ; i++ )
for( int j=0 ; j<N ; j++ )
cin>>map[i][j];
for( int i=0 ; i<N ; i++ ){ //i遍历每一行
for( int j=i ; j<N ; j++ ){ //j遍历i之后的每一行
for( int m=0 ; m<N ; m++ ){ //m是对于每一列来说
s[m] = 0; //初始化
for( int k=i ; k<=j ; k++ )
s[m] += map[k][m]; //叠加和
}
ans = dp( ); //求最大子序列之和
if( ans>max )
max = ans;
}
}
cout<<max<<endl;
}
return 0;
}
int dp( void ){
int sum=0 , max=0;
for( int i=0 ; i<N ; i++ ){ //O(n)的复杂度
sum += s[i];
if( sum < 0 ) //小于0就赋值为0
sum = 0;
if( sum > max )
max = sum; //记录最大值
}
return max;
}
代码分析:从上面的这段代码我们能很明显地看出时间复杂度很高,主函数里for嵌套4层,并且求和的阶段s[m]+=map[k][m]是最频繁的一句代码,求和部分很频繁,为了节约时间,我改成了二维的树状数组来实现求和,因为树状数组求和的复杂度时O(logn)的,比起O(n)的普通求和节约了不少时间
源代码二(二维树状数组求和):
#include<iostream>
using namespace std;
const int MAX = 100;
int map[MAX][MAX];
int s[MAX] , C[MAX][MAX] , num , N;
//C是求和的累加数组
int dp( void );
int lowbit( int n ); //管辖域
int sum( int col , int n ); //返回A[1]到A[n]的和
void add( int k , int col , int n ); //更新C数组的个元素
int main( ){
int ans , max;
while( cin>>N ){
max = 0;
memset( C , 0 , sizeof( C ) );
for( int i=1 ; i<=N ; i++ ) //下标改成从0开始,因为树状数组不是从0的开始的,否则管辖域为0;
for( int j=1 ; j<=N ; j++ ){
cin>>map[i][j];
add( i , j , map[i][j] );
}
for( int i=1 ; i<=N ; i++ ){
for( int j=i ; j<=N ; j++ ){
for( int m=1 ; m<=N ; m++ )
s[m] = sum( m , j ) - sum( m , i-1 ); //求该段之和
ans = dp( );
if( ans>max )
max = ans;
}
}
cout<<max<<endl;
}
return 0;
}
int dp( void ){
int sum=0 , max=0;
for( int i=0 ; i<N ; i++ ){ //和上一个函数一样
sum += s[i];
if( sum < 0 )
sum = 0;
if( sum > max )
max = sum;
}
return max;
}
int lowbit( int n ){
return n & ( -n ); //管辖域就是n对应的二进制数从后往前第一个1所在位置是k(第一个位置是0),则管辖域就是2^k
}
void add( int k , int col , int n ){
while( k <= N ){
C[col][k] += n; //累加更新
k += lowbit( k ); //"父结点"
}
}
int sum( int col , int n ){
int ss = 0;
while( n ){
ss += C[col][n]; //求和
n -= lowbit( n ); //回到另一个点的管辖域范围内
}
return ss;
}
代码分析:由代码对比,很明显地看出树状数组的下标需要从1开始,从0开始是实现不了的(至少说不好实现),树状数组将求和的复杂度从O(n)降为了O(logn),但是得多开辅助存储空间C数组,这也就是经常遇到的消耗空间来节省时间(动态规划从一定意义上说就是部分深度优先搜索的节省消耗空间来节省时间的优化),对树状数组的发明者表示衷心地佩服!