Luogu_1565_牛宫_(最大子矩阵)
描述
http://www.luogu.org/problem/show?pid=1565
给出一个n*m的矩阵,求最大的且和值为正的子矩阵.
分析
很容易想到的是用前缀和维护,暴力枚举左上角和右下角,这样的复杂度是O(n^4)的.(虽然洛谷上这道题也能过)
一种神奇的方法:用前缀和记录每一行的前缀和.枚举的时候先枚举左右端点,然后分别算出左右端点之间每一行的和值,把一行看作一个单元(将一行压缩成一个点),求以行为单元的前缀和.然后前缀和相减可以得到子矩阵.而题目要求子矩阵的和值要为正,所以必须是值大的前缀和减值小的(相等不能减),所以把前缀和排个序.对于子矩阵t[i]-t[j](t是前缀和),t[i]必须大于t[j],而且t[i]代表的前缀和要尽可能"靠下",这样子矩阵才能尽可能大.所以我们排序后扫一遍前缀和,记录当前前缀和t[i]之前的(比t[i]大的)前缀和中最靠下的位置down,然后用down-t[i].id表示两个前缀和之差代表的子矩阵的高度(可能为负),记录最大高度h,最后乘以底边的长度(j-i+1)即可.
注意:
1.前缀和要加上0.
2.相同的不能相减,所以相同的前缀和把序号小的放在上面,这样减出来的就是负的.
1 #include <bits/stdc++.h> 2 #define ll long long 3 using namespace std; 4 5 const int maxn=200+5; 6 int n,m; 7 ll ans; 8 ll s[maxn][maxn]; 9 10 struct node{ 11 ll x,id; 12 node(){} 13 node(ll x,ll id):x(x),id(id){} 14 }t[maxn]; 15 bool cmp(node x,node y){ 16 if(x.x==y.x) return x.id<y.id; 17 return x.x>y.x; 18 } 19 void solve(){ 20 for(int i=1;i<=m;i++) 21 for(int j=i;j<=m;j++){ 22 for(int k=1;k<=n;k++) t[k]=node(t[k-1].x+s[k][j]-s[k][i-1],k); 23 t[0]=node(0,0); 24 sort(t,t+n+1,cmp); 25 ll down=t[0].id,h=0; 26 for(int k=0;k<=n;k++){ 27 h=max(h,down-t[k].id); 28 down=max(down,t[k].id); 29 } 30 ans=max(ans,h*(j-i+1)); 31 } 32 printf("%lld\n",ans); 33 } 34 void init(){ 35 scanf("%d%d",&n,&m); 36 for(int i=1;i<=n;i++) 37 for(int j=1;j<=m;j++){ 38 int t; scanf("%d",&t); 39 s[i][j]=s[i][j-1]+t; 40 } 41 } 42 int main(){ 43 init(); 44 solve(); 45 return 0; 46 }