【jzoj】省选模拟2018.8.23 a 简单的暴力

Description
给定一个n×m 的 01 矩阵,求包含[l,r]个 1 的子矩形个数。
Input
第一行,两个正整数n,m。
接下来n 行,每行一个长度为 m 的 01 串,表示给定的矩阵。接下来一行,两个自然数 l,r。
Output
第一行,一个整数代表答案。
Sample Input
2 3
100
011
2 3
Sample Output
5
Data Constraint
保证0≤l≤r≤n×m
Data Constraint

显然,看到这道题,我们很容易想到可以直接暴力枚举所有情况,也就是枚举矩形的任意两个对角点。
但是,很显然,这种枚举方案的复杂度可以达到 O((nm)²),并不可取。
那么,我们会想到曾经也有这样一道题,也是像现在这样,枚举一个矩形,但是我们没办法直接暴力枚举。这道题就是旅游路线
所以,我们也选择这种类似的方案,因为n比较小,所以我们可以尝试枚举两遍n来把矩形限制在一个大矩形中,然后扫一遍m,来得到答案。
当然,我们会发现,这道题和之前的旅游路线并不一样,我们在扫的过程中,我们会发现,我们需要不断的比较之前的1~m-1列,比较来确保这m-1列哪些满足我们的答案,也就是其中1的个数达到[l,r]。
首先,我们可以选择使用二维前缀和,用一个数组sum[i][j]记录下(1,1) (i,j)这两个点在矩阵中形成的矩形所框住的1的个数,那么,我们就可以在极快的情况下求出任意一个矩形所含的1的个数。
然后,我们为了减轻工作量,我们需要尝试一些减轻复杂度的方法。
一个比较好的方法就是,使用两个指针h,t,一个指针指向满足条件的情况下最前的一列,另一个指向满足条件情况下最后的一列。
可以采取这种办法,是因为根据题目的要求,我们需要统计满足1个数量的矩形的个数,而随着一个矩形的扩大,它的1的数量必然是增加的,所以,对于第j列为此时枚举的矩形的右边,如果我们已经确定了i列到j列间的1的个数在[l,r]中,k列到j列的1也在[l,r]中,并且满足第i列比第k列小,那么,矩形的一边为j的情况下,另一边可以为第i列到第k列间的任意一列。
所以,我们就可以通过寻找边界的情况快速找到矩形的右边固定情况所产生的答案。然后将矩形的右边向右移动,判断矩形的左边的范围是否变动,然后将这个范围继续加入答案,就可以解出答案了。
但是,我们观察数据范围,必须要留意一个 l=0,r=n×m的情况,这个情况下,任意一个矩形都可以加入答案,所以,我们会知道这种方法解决答案,极有可能会出现超时的状况,所以,我们直接动笔计算出这个情况下它的答案。
由于这种情况下,任意一个矩形都满足条件,所以,我们实际上是在计算可以划分出多少个矩形,最简便的方法就是通过组合数学的定律,分别计算出左端、右端、上端、下端的可能,就可以得到答案了。

#include <bits/stdc++.h>

using namespace std;

int n,m,head,tail,l,r,k;
int sum[31][50010];
long long ans;

inline void read(int q)
{
    char c=getchar();
    int now=0;
    while ((c<'0')||(c>'1'))
        c=getchar();
    while ((c=='0')||(c=='1'))
        sum[q][++now]=c-'0'+sum[q-1][now]+sum[q][now-1]-sum[q-1][now-1],c=getchar();
    return ;
}

int getsum(int ,int ,int ,int );

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=n;i++)
        read(i);
    scanf("%d%d",&l,&r);
    if (l==0&&r==n*m)
        goto put;
    for (int i=1;i<=n;i++)
        for (int j=i;j<=n;j++)
        {
            head=tail=1;
            for (k=1;k<=m&&getsum(i,j,tail,k)<l;k++);
            for (;k<=m;k++)
            {
                while (getsum(i,j,head,k)>r)
                    head++;
                while (getsum(i,j,tail+1,k)>=l)
                    tail++;
                ans+=(long long )tail-(long long )head+(long long )1;
            }
        }
    printf("%lld",ans);
    return 0;
    put :
        printf("%lld",((long long )n*(n+1)*m*(m+1))>>2);
    return 0;
} 

int getsum(int n,int s,int w,int e)
{
    return (sum[s][e]-sum[s][w-1]-sum[n-1][e]+sum[n-1][w-1]);
}

猜你喜欢

转载自blog.csdn.net/CutieDeng/article/details/81978794