gmoj 6860. 【2020.11.14提高组模拟】鬼渊传说

题目

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,m300

数据点编号 n ≤ n\le n m ≤ m\le m特殊性质
1,255
3,42020
5,6300300保证没有两个黑色格子相邻
7,8300300保证任意子矩形不存在空腔
9,10300300

题解

考场上想到可以通过搜索白色连通块得到包含空腔的最小矩形,但是其它都没有想到。

看到 n , m ≤ 300 n,m\le 300 n,m300,容易想到要用 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 VE+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=lr1hori+i=lrveri,=i=lrnodei,=i=1r1squi

这些 ∑ \sum 又可以用前缀和来处理。

但是对于一个已知的 l l l,怎么快速求出一个区间内有多少个 r r r使得 V − E + F = 1 V-E+F=1 VE+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=1l1(hori+veri),E1=verr+i=1r1(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 r1
只要把右边界的限制挂在 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;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值