zoukankan      html  css  js  c++  java
  • T3118 01完美矩阵【计数,前缀和,差分,好题】

    Online Judge:未知

    Label:好题,计数,前缀和

    题目描述

    一个01矩形被称为是完美01矩形,如果满足下面3个条件:

    (1)它的四条边上都是1

    (2)内部(除了4条边)的0和1的个数之差不超过1

    (3)大小至少是2*2

    给定一个01矩阵,求可以在其中圈出多少完美01矩形。

    输入

    第一行两个整数n和m

    接下来n行,每行m个数,0或者1.

    输出

    输出完美01矩形的个数。

    样例

    Input

    4 4
    1 1 1 1
    1 0 1 1
    1 1 0 1
    1 1 1 1
    
    5 5
    1 0 1 1 1
    1 0 1 0 1
    1 1 0 1 1
    1 0 0 1 1
    1 1 1 1 1
    

    Output

    3
    
    3
    

    Hint

    对于30%的数据,n和m的数据范围([1,20]);
    对于60%的数据,n和m的数据范围([1,100]);
    对于100%的数据,n和m的数据范围([1,300]).

    题解

    60pts

    (O(N^4))随便怎么敲都可以。直接枚举矩形上的两角。对于随机数据,下面的代码跑的飞快。但是如果有大量1就挂掉了。

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=302;
    int a[N][N],n,m;
    int sum[N][N];
    int le[N][N],ri[N][N],up[N][N],down[N][N];
    inline int read(){
        int x=0;char c=getchar();
        while(c<'0'||c>'9')c=getchar();
        while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
        return x;
    }
    void pre(){
        register int i,j;
        for(i=1;i<=n;++i){
            int lst=0;
            for(j=1;j<=m;++j){
                if(a[i][j])le[i][j]=++lst;
                else lst=0;
            }
            lst=0;
            for(j=m;j>=1;j--){
                if(a[i][j])ri[i][j]=++lst;
                else lst=0;
            }
        }
        for(j=1;j<=m;++j){
            int lst=0;
            for(i=1;i<=n;++i){
                if(a[i][j])up[i][j]=++lst;
                else lst=0;
            }
            lst=0;
            for(i=n;i>=1;i--){
                if(a[i][j])down[i][j]=++lst;
                else lst=0;
            }
        }
         
        for(i=1;i<=n;++i)for(j=1;j<=m;++j){
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
        }
    }
    inline int calc(int x,int y,int x2,int y2){
        int res=sum[x2][y2]-sum[x-1][y2]-sum[x2][y-1]+sum[x-1][y-1];
        return res;
    }
    namespace p60{
        void solve(){
            register int i,j,k,l;
            ll ans=0;
            for(i=1;i<=n;++i)for(j=1;j<=m;++j)if(a[i][j]){
                for(k=i+1;k<=i+down[i][j]-1;k++)for(l=j+1;l<=j+ri[i][j]-1;l++)if(a[k][l]){
                    if((k-up[k][l]+1<=i)&&(l-le[k][l]+1<=j)){ 
                        int tmp=calc(i+1,j+1,k-1,l-1),o=(k-i-1)*(l-j-1);
                        if(abs(o-2*tmp)<=1)++ans;    
                    }
                }
            }
            printf("%lld
    ",ans);
        }
    }
    int main(){
    //  freopen("matrix.in","r",stdin);
    //  freopen("matrix.out","w",stdout);
        n=read(),m=read();
        for(register int i=1;i<=n;++i)for(register int j=1;j<=m;++j)a[i][j]=read();
        pre();
        p60::solve();
    }
    

    100pts

    看数据范围,正解复杂度应该是(O(N^3))的。

    对于这类矩阵的计数题,有一个常见套路做法:

    (O(N^2))时间枚举上下行边界,对于列,在(O(N))时间内扫一遍,用前缀和等维护计数。


    step0.预处理

    先预处理一个东西s[i][j],表示(i,j)的二维前缀和,但与普通的前缀和不同,在这里将0计为-1,将1计为+1。这样处理有什么好处呢?下面会提到。

    for(i=1;i<=n;i++)for(j=1;j<=m;j++){
    		scanf("%d",&a[i][j]);
    		s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(a[i][j]==1?1:-1);
    	}
    

    这样可以求得以(x,y)为左上角,(x2,y2)为右下角的矩形"面积"(为了方便描述,下面都用“面积”表示矩形内的01个数差)。比如这个矩形内0个数比1个数多5,则面积为-5,如果0个数等于1个数,则面积为0。

    inline int Area(int x,int y,int x2,int y2){
    	return s[x2][y2]-s[x-1][y2]-s[x2][y-1]+s[x-1][y-1];
    }
    

    step1.统计

    先枚举上下边。

    for(i=1;i<n;i++)for(j=i+1;j<=n;j++){//上下边 
    

    接下里要考虑在(O(N))时间内求得以(i,j)为上下边的01完美矩阵个数。


    先思考一种比较low的做法。

    下面代码中,直接暴力统计左右边在([k,l])范围内的符合条件的矩形个数。虽然看起来是(O(N^3))的,但实际是(O(N^2))的(因为后面(k=l+1)跳了一下)。

    for(k=1;k<=m;k++)if(a[i][k]&&a[j][k]){
    	int l=k;
    	while(l+1<=m&&a[i][l+1]&&a[j][l+1])l++;//找到
    	if(k==l)continue;	
    	
        //暴力统计
        int o1,o2;
        for(o1=k;o1<=l;o1++)for(o2=o1;o2<=l;o2++){
            if(是01完美矩形(i,j,o1,o2))计数;
        }
        
    	k=l+1;	
    }
    

    瓶颈在于这个(O(N^2))的暴力统计。

    解决方法就是用到之前预处理的前缀和。见下面代码。

    利用差分思想。每次找到合法的列,查询前面的前缀和是否有和它相差1以内的,计入答案。然后把自己的前缀和加入统计。

    for(p=k;p<=l;p++)if(Area(i,p,j,p)==j-i+1){	
    	int pre=Area(i+1,k+1,j-1,p-1)+base,now=Area(i+1,k+1,j-1,p)+base;	
    	ans+=cnt[pre-1]+cnt[pre]+cnt[pre+1];
    	cnt[now]++;
    }
    

    由于矩形面积可能为负,所以要加上一个基量(base)

    而且计算完([k,l])内的矩形后,还要清空(cnt[])数组,清空代码如下,和上面反一下:

    for(p=k;p<=l;p++)if(Area(i,p,j,p)==j-i+1){
    	int now=Area(i+1,k+1,j-1,p)+base;
    	cnt[now]--;
    }	
    

    综上时间复杂度为(O(N^3)),完整代码如下:

    //枚举上下边,前缀和统计 
    #include<bits/stdc++.h>
    typedef long long ll;
    using namespace std;
    const int N=305,base=N*N;
    int n,m,a[N][N],s[N][N],cnt[2*N*N];
    ll ans=0;
    inline int Area(int x,int y,int x2,int y2){
    	return s[x2][y2]-s[x-1][y2]-s[x2][y-1]+s[x-1][y-1];
    }
    int main(){
    	scanf("%d%d",&n,&m);
    	register int i,j,k,p; 
    	for(i=1;i<=n;i++)for(j=1;j<=m;j++){
    		scanf("%d",&a[i][j]);
    		s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+(a[i][j]==1?1:-1);
    	}
    	for(i=1;i<n;i++)for(j=i+1;j<=n;j++){//上下边 
    		for(k=1;k<=m;k++)if(a[i][k]&&a[j][k]){
    			int l=k;
    			while(l+1<=m&&a[i][l+1]&&a[j][l+1])l++;
    			if(k==l)continue;	
    			for(p=k;p<=l;p++)if(Area(i,p,j,p)==j-i+1){	
    				int pre=Area(i+1,k+1,j-1,p-1)+base,now=Area(i+1,k+1,j-1,p)+base;	
    				ans+=cnt[pre-1]+cnt[pre]+cnt[pre+1];
    				cnt[now]++;
    			}
    			for(p=k;p<=l;p++)if(Area(i,p,j,p)==j-i+1){
    				int now=Area(i+1,k+1,j-1,p)+base;
    				cnt[now]--;
    			}	
    			k=l+1;	
    		}
    	}
    	printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    OpenGL---------BMP文件格式
    OpenGL———混合的基本知识
    OpenGL------显示列表
    OpenGL---------光照的基本知识
    OpenGL学习--------动画制作
    OpenGL------三维变换
    OpenGL学习--------颜色的选择
    OpenGL学习-------点、直线、多边形
    Windows X64汇编入门(1)
    x86 x64下调用约定浅析
  • 原文地址:https://www.cnblogs.com/Tieechal/p/11626714.html
Copyright © 2011-2022 走看看