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 }
View Code

 

posted @ 2016-05-25 19:15  晴歌。  阅读(374)  评论(0编辑  收藏  举报