2020牛客暑期多校训练营The Escape Plan of Groundhog(暴力,前缀和)

The Escape Plan of Groundhog

题目描述

2020牛客暑期多校训练营The Escape Plan of Groundhog(暴力,前缀和)_第1张图片

样例

input:
4 4
1 1 1 1
1 0 1 1
1 1 0 1
1 1 1 1
output:
3
input:
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

题目大意

给定一个01矩阵,要求在里面找到一个子矩阵,满足:
1、这个矩阵的周围的一圈是1。
2、这个矩除了周围一圈,中间01数量之差最多为1。
3、这个矩阵至少为2*2。
求会有多少矩阵。

分析

数据范围是500,容易想到用 n 3 n^3 n3做法。
先来想最暴力的:
n 4 n^4 n4:枚举左上和右下端点,然后用一个矩阵的前缀和搞出里面的01之差,然后判断是否满足条件。枚举 n 4 n^4 n4,判断 O ( 1 ) O(1) O(1),总 O ( n 4 ) O(n^4) O(n4) T L E TLE TLE。那么怎么压掉一维?
接下来是官方的方式:
首先固定一个矩阵的上边界 i i i和下边界 j j j,然后搞一个 k k k从1到m遍历过去,每次判断是否与之前的形成合法的上下边界,即判断是否有连续的等长的1,然后再判断之前的竖向的全是1的一列和当前的全是1的一列是否合法,之后再判断一下中间是否合法即可。

这个方式的唯一问题就是怎么快速判断。同样的,我们采用前缀和。我们设 d p dp dp为竖向的前缀和,可以快速判断一列是否为1。然后设一个 p r e pre pre表示当前这个区间的横向的前缀和,这样就可以快速地求出每段区间内01个数之差。
那么我们只要枚举 i , j i,j i,j,然后从左到右扫过去。碰到一列如果全是1,那么就把当前的前缀和与上一次相比较。然后合法就记录答案即可。
但是,可能会有以下的情况:
11111111111 … 11111111111\dots 11111111111
1 1 1 … 1\qquad 1\qquad 1\dots 111
1 1 1 … 1\qquad 1\qquad 1\dots 111
11111111111 … 11111111111\dots 11111111111
如果每次都向前扫一遍,遇到极端数据会很慢。
所以要采用一种快速的方法。这里我们可以用桶记录一下01的差。那么不妨我们把0在里面记成-1。这样求出的前缀和就是01的差了。然后对于我当前的这个区间内01差,答案加上与之相差1的桶中的个数即可。
看代码:

代码

#include
#define ll long long
#define inf 1<<30
using namespace std;
const int MAXN=510;
int n,m,a[MAXN][MAXN];
int dp[MAXN][MAXN],pre[MAXN],t[MAXN*MAXN];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            scanf("%d",&a[i][j]);
            dp[i][j]=dp[i-1][j]+(a[i][j]==1?1:-1);//计算竖向的前缀
        }
    }
    int last=inf,ans=0;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){last=1;//表示上一个区间的结束位置
            for(int k=1;k<=m;k++){
                if(!a[i][k]||!a[j][k]){
                    for(int l=last;l<=k;l++)
                        if(dp[j][l]-dp[i-1][l]==j-i+1)
                            t[pre[l]]--;
                    last=k+1;continue;
                }//如果和之前的区间断开,那么应该从新开始统计
                if(dp[j][k]-dp[i-1][k]==j-i+1) ans+=t[pre[k-1]+1]+t[pre[k-1]]+t[pre[k-1]-1];
                //如果这一列全是1那么就要加上答案,相差1的都要加
                pre[k]=pre[k-1]+dp[j-1][k]-dp[i][k];//横向的前缀,区间前缀的求法
                if(dp[j][k]-dp[i-1][k]==j-i+1) t[pre[k]]++;
                //将当前的前缀加入桶中
            }
            for(int k=last;k<=m;k++)
                if(dp[j][k]-dp[i-1][k]==j-i+1) t[pre[k]]--;
            //这本质上就是个清零,但是我改成memset样例都不过了,有没有大佬解释一下……
        }
    }printf("%d\n",ans);
}

END

对了,这么久了,有人知道我搞个END有什么用吗?

你可能感兴趣的:(2020牛客多校,暴力,前缀和)