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\n",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\n",ans);
}

T3118 01完美矩阵【计数,前缀和,差分,好题】的更多相关文章

  1. HDU 6336 (规律 + 二维矩阵的前缀和妙用)

    题目 给出长度为n 的A矩阵 , 按 int cursor = 0; for (int i = 0; ; ++i) { for (int j = 0; j <= i; ++j) { M[j][i ...

  2. Codeforces 479E Riding in a Lift:前缀和/差分优化dp

    题目链接:http://codeforces.com/problemset/problem/479/E 题意: 有一栋n层的房子. 还有一个无聊的人在玩电梯,每次玩电梯都会从某一层坐到另外一层. 他初 ...

  3. 北京区域赛I题,Uva7676,A Boring Problem,前缀和差分

    转载自https://blog.csdn.net/weixin_37517391/article/details/83821752 题解 其实这题不难,只要想到了前缀和差分就基本OK了. 我们要求的是 ...

  4. [CSP-S模拟测试]:stone(结论+桶+前缀和+差分)

    题目描述 $Cab$有两行石子,每个石子上有一个字母,为$'C''A''B'$中的一个.一开始,在每行第一个石子上站着一只$lucky$,$Cab$每次可以选择一个字母,使得所站石子上字母为该字母的$ ...

  5. HDU-6514 Monitor(二维前缀和+差分)

    http://acm.hdu.edu.cn/showproblem.php?pid=6514 Problem Description Xiaoteng has a large area of land ...

  6. 【T-SQL基础】01.单表查询-几道sql查询题

    概述: 本系列[T-SQL基础]主要是针对T-SQL基础的总结. [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础]02.联接查询 [T-SQL基础]03.子查询 [T-SQL基础 ...

  7. luogu 1360 阵容均衡(前缀和+差分+hash)

    要求一段最大的区间里每个能力的增长值是一样的. 我们首先求一遍前缀和,发现,如果区间内[l,r]每个能力的增长值是一样的话,那么前缀和[r]和[l-1]的差分也应该是一样的. 那么我们把前缀和的差分h ...

  8. CF 816B Karen and Coffee【前缀和/差分】

    To stay woke and attentive during classes, Karen needs some coffee! Karen, a coffee aficionado, want ...

  9. HihoCoder1673 : 01间隔矩阵([Offer收割]编程练习赛41)(单调队列)

    描述 给定一个N × M的01矩阵,小Hi希望从中找到一个01间隔的子矩阵,并且子矩阵的面积越大越好. 例如对于 0101010 1000101 0101010 1010101 0101010 在右侧 ...

随机推荐

  1. gnome/KDE安装,gnome出现问题,重新安装nvdia驱动, Linux(CentOS7) NVIDIA GeForece GTX 745 显卡驱动

    新安装显示gtx745驱动NVIDIA-Linux-x86_64-346.59.run, yum groupremove kde-desktop yum groupinstall "Desk ...

  2. VScode中写vue代码 Ctrl+/添加注释失效

    1.点击列表的文件——>首选项——>键盘快捷方式,在里面查看 Ctrl+/ 是否有冲突 2.查看右下角的选择语言模式是否是Vue,如下图

  3. [转] .htaccess实现www 与没有www之间的重定向

    建站过程中有时候我们需要做这些设置 1.访问www 直接重定向到没有www上或者反过来,那么怎么通过.htaccess文件来实现呢. 1.首先服务器要支持Rewrite重写 2.创建.htaccess ...

  4. Firefox Developer Edition 是专为开发者设计

    Firefox Developer Edition 当前是基于 Firefox 35.0a2,这款全新的浏览器包括内建调试功能,集成类似于Firefox火狐工具适配器的专用工具,并在浏览器当中内建We ...

  5. 4.8 this关键字

    /** * 测试this * @author Hank * */ /* 创建一个对象分为如下四步: 1.分配对象空间,并将对象成员变量初始化为0或空 2.执行属性值的显示初始化 3.执行构造方法 4. ...

  6. uoj140 【UER #4】被粉碎的数字

    题目 看起来就像是数位\(\rm dp\) 不妨从竖式乘法的角度来考虑这个问题 为了方便处理进位,我们得从低位向高位填数 设\(dp[i][0/1][j][p][t]\)表示填到了第\(i\)位,卡不 ...

  7. 【笔记篇】单调队列优化dp学习笔记&&luogu2569_bzoj1855股票交♂易

    DP颂 DP之神 圣洁美丽 算法光芒照大地 我们怀着 崇高敬意 跪倒在DP神殿里 你的复杂 能让蒟蒻 试图入门却放弃 在你光辉 照耀下面 AC真心不容易 dp大概是最经久不衰 亘古不化的算法了吧. 而 ...

  8. js清空input file值

    项目进行导入操作,如果第一次导入某个文件会触发导入操作,但是第二次导入重复该文件,不会触发操作. 分析了原因,是因为上一次file里选择的文件路径值与本次选择的文件路径值是一样的,值没有改变所以导致f ...

  9. thinkphp url生成

    为了配合所使用的URL模式,我们需要能够动态的根据当前的URL设置生成对应的URL地址,为此,ThinkPHP内置提供了U方法,用于URL的动态生成,可以确保项目在移植过程中不受环境的影响. 定义规则 ...

  10. 单调栈——cf777E

    傻逼单调栈啊我怎么想了半天dp #include <bits/stdc++.h> using namespace std; typedef long long LL; typedef st ...