HDU 1081 To The Max【dp,思维】
题意:给定二维矩阵,求数组的子矩阵的元素和最大是多少。
题解:这个相当于求最大连续子序列和的加强版,把一维变成了二维。
先看看一维怎么办的:
1 int getsum() 2 { 3 int tot=0; 4 int ans=-1e9; 5 for(int i=1;i<=n;i++){ 6 if(tot<0) tot=0; 7 tot+=a[i]; 8 if(tot>ans) 9 ans=tot; 10 } 11 return ans; 12 }
这种做法太棒了!短短几行,就能解决最大子序列和这个问题。其实这几行代码值得深思。而且这是个在线算法,输入数据及时能给出结果,感觉不能归于动归解法。
但当求解这道题时,就不知道怎么办了。我当时受到以前做的一道关于求01矩阵最大0子矩阵面积的题的启发,想先预处理每行得到前缀和数组,然后再想办法dp。可是dp状态和方程一直找不好。后来看到别人的解法才明白,他们其实也是预先处理的每行的前缀和。他们的想法就是先求出每行的前缀和数组sum[][],然后依次枚举前k行的第i列到第j列的和,求最大值。感觉就像是枚举出了每一个子矩阵,像暴力>_<.
#include<iostream> #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int MAXN = 107; int sum[MAXN][MAXN]; int main() { int N, tmp; while (scanf("%d", &N) == 1) { memset(sum, 0, sizeof(sum)); for (int i = 1; i <= N; i++) for (int j = 1; j <= N; j++) { scanf(" %d", &tmp); sum[i][j] = sum[i][j - 1] + tmp;//预处理得到每行的前缀和 } int ans = -1e9; for (int i = 1; i <= N; i++) {//从第i列 for (int j = i; j <= N; j++) {//到第j列 int tot = 0; for (int k = 1; k <= N; k++)//前k行 { if (tot < 0) tot = 0; tot += sum[k][j] - sum[k][i - 1];//第i列到第j列的和 if (ans < tot) ans = tot; } } } printf("%d\n", ans); } return 0; }
当然,可以前k行第i列到第j列就可以前k列第i行到第j行,其实是一样的:
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN = 107; 7 int sum[MAXN][MAXN]; 8 9 int main() 10 { 11 int N,tmp; 12 while (scanf("%d",&N)==1) 13 { 14 memset(sum, 0, sizeof(sum)); 15 for(int i=1;i<=N;i++) 16 for (int j = 1; j <= N; j++) 17 { 18 scanf(" %d", &tmp); 19 sum[i][j] = sum[i-1][j] + tmp; 20 } 21 int ans = -1e9; 22 for (int i = 1; i <= N; i++) { 23 for (int j = i; j <= N; j++) { 24 int tot = 0; 25 for (int k = 1; k <= N; k++) 26 { 27 if (tot < 0) tot = 0; 28 tot += sum[j][k] - sum[i-1][k]; 29 if (ans < tot) 30 ans = tot; 31 } 32 } 33 } 34 printf("%d\n", ans); 35 } 36 return 0; 37 }
也有人按照矩阵压缩的想法写,我觉得实际上还是上面的做法,而且上面的想法更容易理解。因为这里所谓矩阵压缩也就是几行加在一起求最大连续子序列。虽殊途同归,但毕竟想法不一样,代码上就会稍微有点差别:
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN=105; 7 int a[MAXN][MAXN],tmp[MAXN]; 8 int N; 9 10 int getsum() 11 { 12 int tot=0,mx=0; 13 for(int i=1;i<=N;i++){ 14 tot+=tmp[i]; 15 if(tot>mx) mx=tot; 16 if(tot<0) tot=0; 17 } 18 return mx; 19 } 20 21 int main() 22 { 23 while(scanf("%d",&N)==1) 24 { 25 int temp; 26 for(int i=1;i<=N;i++) 27 for(int j=1;j<=N;j++) 28 scanf("%d",&a[i][j]); 29 int ans=-1e9; 30 for(int i=1;i<=N;i++) 31 { 32 memset(tmp,0,sizeof(tmp)); 33 for(int j=i;j<=N;j++) 34 { 35 for(int k=1;k<=N;k++) 36 tmp[k]+=a[j][k]; 37 int tot=getsum(); 38 if(ans<tot) 39 ans=tot; 40 } 41 } 42 printf("%d\n",ans); 43 } 44 45 return 0; 46 }
还有人用树状数组写,我觉的也很棒。没想到我第一次用到二维树状数组是再做dp题的时候。。。虽然这是伪装成二维树状数组的,因为只更新了一维。按照我的理解二维树状数组就是存了若干个一维树状数组的和。
1 #include<iostream> 2 #include<cstring> 3 #include<cstdio> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN=105; 7 int sum[MAXN][MAXN]={0}; 8 int N; 9 10 int Lowbit(int x) 11 { 12 return x&-x; 13 } 14 15 void Add(int i,int j,int val) 16 { 17 while(j<=N){ 18 sum[i][j]+=val; 19 j+=Lowbit(j); 20 } 21 return; 22 } 23 24 int Sum(int k,int x) 25 { 26 int cnt=0; 27 while(x>0){ 28 cnt+=sum[k][x]; 29 x-=Lowbit(x); 30 } 31 return cnt; 32 } 33 34 int main() 35 { 36 while(scanf("%d",&N)==1) 37 { 38 int tmp; 39 memset(sum,0,sizeof(sum)); 40 for(int i=1;i<=N;i++) 41 for(int j=1;j<=N;j++){ 42 scanf("%d",&tmp); 43 Add(i,j,tmp); 44 } 45 int ans=-1e9; 46 for(int i=1;i<=N;i++) 47 { 48 for(int j=i;j<=N;j++){ 49 int tot=0; 50 for(int k=1;k<=N;k++){ 51 if(tot<0) tot=0; 52 tot+=Sum(k,j)-Sum(k,i-1); 53 if(ans<tot) 54 ans=tot; 55 } 56 } 57 } 58 printf("%d\n",ans); 59 } 60 61 return 0; 62 }
当然,加个dp数组看起来更像是dp题。
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 using namespace std; 6 const int MAXN = 107; 7 int sum[MAXN][MAXN]; 8 int dp[MAXN]; 9 int N; 10 11 int lowbit(int x) 12 { 13 return x&-x; 14 } 15 16 void add(int i, int j, int d) 17 { 18 while (j <= N) 19 { 20 sum[i][j] += d; 21 j += lowbit(j); 22 } 23 return; 24 } 25 26 int Sum(int k, int x) 27 { 28 int cnt = 0; 29 while (x>0) 30 { 31 cnt += sum[k][x]; 32 x -= lowbit(x); 33 } 34 return cnt; 35 } 36 37 int main() 38 { 39 int tmp; 40 while (scanf("%d",&N)==1) 41 { 42 memset(sum, 0, sizeof(sum)); 43 for(int i=1;i<=N;i++) 44 for (int j = 1; j <= N; j++) 45 { 46 scanf(" %d", &tmp); 47 add(i, j, tmp); 48 } 49 int ans = -1e9; 50 for (int i = 1; i <= N; i++) { 51 for (int j = i; j <= N; j++) { 52 memset(dp, 0, sizeof(dp)); 53 for (int k = 1; k <= N; k++) { 54 tmp = Sum(k, j) - Sum(k, i-1); 55 if (dp[k - 1] > 0) 56 dp[k] = dp[k - 1] + tmp; 57 else 58 dp[k] = tmp; 59 ans = max(ans, dp[k]); 60 } 61 } 62 } 63 printf("%d\n", ans); 64 } 65 return 0; 66 }
参考博客(感谢~):
【1】:http://blog.csdn.net/acmman/article/details/38580931?spm=5176.100239.blogcont18950.3.TbcH0h
【2】:http://www.cnblogs.com/cenariusxz/p/4309627.html
【3】:http://www.cnblogs.com/gaigai/archive/2012/03/04/2379728.html