题目
Time Limits: 1000 ms
Memory Limits: 518400 KB
简要题意
给出一个网格图,每个格子有黑白二色,求有多少个子矩形满足将其挖出来后恰好有一个黑色四连通块且不存在由白色格子组成的空腔。
空腔:某个白色格子在空腔内当且仅当其不能通过上下左右四方向走到边界。
输入
第一行两个数
n
,
m
n,m
n,m,表示网格图的大小。
接下来 n 行每行一个长为 m 的 01 串,表示网格图的颜色情况(0 为白 1 为黑)
输出
一行一个数表示答案。
样例 1 输入
4 4
1111
1101
1001
1111
样例 1 输出
83
数据规模与约定
对于 100% 数据满足 0 < n , m ≤ 300 0<n,m\le 300 0<n,m≤300。
数据点编号 | n ≤ n\le n≤ | m ≤ m\le m≤ | 特殊性质 |
---|---|---|---|
1,2 | 5 | 5 | |
3,4 | 20 | 20 | |
5,6 | 300 | 300 | 保证没有两个黑色格子相邻 |
7,8 | 300 | 300 | 保证任意子矩形不存在空腔 |
9,10 | 300 | 300 |
题解
考场上想到可以通过搜索白色连通块得到包含空腔的最小矩形,但是其它都没有想到。
看到 n , m ≤ 300 n,m\le 300 n,m≤300,容易想到要用 O ( n 3 ) O\left(n^3\right) O(n3)的方法做(这里的 n , m n,m n,m同阶,因此可以这样写,下同)。一个很简单的思路就是依次枚举子矩形的上、下、左边界,看一下有多少个合法的右边界。
分析一下题目,发现这道题有2个难点:一是子矩形中有且仅有一个黑色连通块,二是子矩形不包含含有空腔的最小矩形。
第一个——有且仅有一个黑色连通块的子矩形
令
E
,
F
,
V
,
X
E,F,V,X
E,F,V,X分别表示子矩形中连接两个'1'
的边的数量、由'1'
组成的简单四元环的数量、'1'
的数量、连通块的数量。由欧拉定理,可得
V
−
E
+
F
=
X
V-E+F=X
V−E+F=X
E
,
F
,
V
E,F,V
E,F,V都是可以用前缀和维护得出的。
具体来说,可以设
h
o
r
i
,
v
e
r
i
,
n
o
d
e
i
,
s
q
u
i
hor_i ,ver_i ,node_i ,squ_i
hori,veri,nodei,squi分别表示这样的图形数量(顶点都表示'1'
):
那么当选择了 [ l , r ] [l,r] [l,r]中所有列时,有 E = ∑ i = l r − 1 h o r i + ∑ i = l r v e r i , V = ∑ i = l r n o d e i , F = ∑ i = 1 r − 1 s q u i \begin{aligned} E&=\sum_{i=l}^{r-1}hor_i +\sum_{i=l}^r ver_i ,\\[2ex] V&=\sum_{i=l}^r node_i ,\\[2ex] F&=\sum_{i=1}^{r-1} squ_i \end{aligned} EVF=i=l∑r−1hori+i=l∑rveri,=i=l∑rnodei,=i=1∑r−1squi
这些 ∑ \sum ∑又可以用前缀和来处理。
但是对于一个已知的
l
l
l,怎么快速求出一个区间内有多少个
r
r
r使得
V
−
E
+
F
=
1
V-E+F=1
V−E+F=1呢?
令
E
0
=
∑
i
=
1
l
−
1
(
h
o
r
i
+
v
e
r
i
)
,
E
1
=
v
e
r
r
+
∑
i
=
1
r
−
1
(
h
o
r
i
+
v
e
r
i
)
E_0 =\sum_{i=1}^{l-1} (hor_i+ver_i),E_1=ver_r + \sum_{i=1}^{r-1} (hor_i+ver_i)
E0=∑i=1l−1(hori+veri),E1=verr+∑i=1r−1(hori+veri),其它
V
0
,
V
1
,
F
0
,
F
1
V_0 ,V_1 ,F_0 ,F_1
V0,V1,F0,F1也是同理。
那么可以通过移项把等号的左边变成没有下标
1
1
1的,右边变成没有下标
0
0
0的。
用一个桶就可以处理了,注意移项后有可能等号两边都为负数,而且桶很大不能每次都memset
。
第二个——不包含含有空腔的最小矩形的子矩形。
令上边界、下边界、左边界分别为
i
,
j
,
k
i,j,k
i,j,k。
可以把所有的含有空腔的最小矩形都用数据结构挂在它的下边界处,当
j
j
j 扫到它时,对于一个上边界大于等于
i
i
i 的矩形,令其左边界为
l
l
l,右边界为
r
r
r,它会限制在
[
1
,
l
]
[1,l]
[1,l]中的
k
k
k。这些
k
k
k所对应的右边界不能超过
r
−
1
r-1
r−1。
只要把右边界的限制挂在
l
l
l上,从右往左枚举
k
k
k就行了。
这道题的细节相对来说比较少。
CODE
#include<cstdio>
#include<cstring>
using namespace std;
#define M 90005
#define N 305
const int dx[4]={1,0,-1,0},dy[4]={0,1,0,-1};
struct area{int up,left,right,nex;}a[M];
int hor[N],ver[N],squ[N],node[N],_h[N],_v[N],_s[N],_n[N],fir[N],lim[N],b[200005];
int time[200005],n,m,x1,y1,x2,y2,cnt,T;
char s[N][N];bool vis[N][N];
void dfs(int x,int y)
{
vis[x][y]=1;
if(x1>x) x1=x;if(x2<x) x2=x;
if(y1>y) y1=y;if(y2<y) y2=y;
for(int i=0,xx,yy;i<4;++i)
{
xx=x+dx[i],yy=y+dy[i];
if(xx&&yy&&xx<=n&&yy<=m&&s[xx][yy]=='0'&&!vis[xx][yy])
dfs(xx,yy);
}
}
inline void add(int x,int y)
{
if(x<0) puts("dd");
if(time[x]<T) time[x]=T,b[x]=y;
else b[x]+=y;
}
inline int qry(int x){return time[x]<T?0:b[x];}
int main()
{
freopen("village.in","r",stdin);
freopen("village.out","w",stdout);
long long ans=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i) scanf("%s",s[i]+1);
for(int i=1;i<=n;++i) for(int j=1;j<=m;++j)
if(s[i][j]=='0'&&!vis[i][j])
{
x1=y1=N,x2=y2=0,dfs(i,j),--x1,--y1,++x2,++y2;
if(x1&&y1&&x2<=n&&y2<=m) a[++cnt]=(area){x1,y1,y2,fir[x2]},fir[x2]=cnt;
}
for(int up=1;up<=n;++up)
{
memset(lim,0x3f,sizeof lim);
memset(hor,0,sizeof hor);
memset(ver,0,sizeof ver);
memset(squ,0,sizeof squ);
memset(node,0,sizeof node);
for(int down=up;down<=n;++down)
{
for(int i=1;i<=m;++i) if(s[down][i]=='1')
{
hor[i]+=s[down][i+1]=='1';
if(up<down)
ver[i]+=s[down-1][i]=='1',
squ[i]+=s[down-1][i]=='1'&&s[down-1][i+1]=='1'&&s[down][i+1]=='1';
++node[i];
}
for(int i=1;i<=m;++i)
_h[i]=_h[i-1]+hor[i],_v[i]=_v[i-1]+ver[i],
_s[i]=_s[i-1]+squ[i],_n[i]=_n[i-1]+node[i];
for(int i=fir[down];i;i=a[i].nex) if(a[i].up>=up)
if(lim[a[i].left]>a[i].right-1) lim[a[i].left]=a[i].right-1;
++T;
for(int l=m,r=m;l;--l)
{
//E=_h[r-1]-_h[l-1]+_v[r]-_v[l-1],V=_n[r]-_n[l-1],F=_s[r-1]-_s[l-1];
add(M+1+_h[l-1]+_v[l]-_n[l]-_s[l-1],1);
if(r>lim[l])
{
while(r>lim[l])
add(M+1+_h[r-1]+_v[r]-_n[r]-_s[r-1],-1),--r;
}
if(l<=r)
{
ans+=qry(M+_h[l-1]+_v[l-1]-_n[l-1]-_s[l-1]);
// printf("(%d,%d)\t(%d,%d):\n",up,l,down,r);
// for(int i=up;i<=down;++i,puts(""))
// for(int j=l;j<=r;++j) printf("%c",s[i][j]);
// puts("");
}
}
}
}
printf("%lld\n",ans);
return 0;
}