题目大意
给出一个01矩阵,这个矩阵有一个特殊的性质: 对于任意两个 \(1\) 之间最多只有 \(1\) 条由 \(1\) 构成的路径.每次询问给出一个矩形范围,查询在这个范围内的联通快个数.
分析
先从给出的性质出发,可以发现如果在所有相邻的 \(1\) 之间连上一条边(双向边)以后这个性质就转化成了在这若干个 \(1\) 所组成的图中不会出现环,在仔细思考一下,不会出现环,而且是双向边,这意味着什么?没错,这是一片森林(由很多树组成),对于每一颗树就是一个联通快了,可以发现这既然是树,那么如果它被残忍地切开后它仍然是一颗树,于是对于矩阵查询并不会对于每个联通块都是一棵树这个性质产生影响,于是就很容易想到树有什么性质,其中有一个非常特殊的性质,一棵有 \(N\) 个节点的树中有且只有 \(N-1\) 条边,那么矩阵中的点的个数 \(-\) 矩阵中相邻的 \(1\) 所形成的边的个数 \(=\) 矩阵中树的个数,至于计算部分,二维前缀和搞一下就好了,可以做到 \(O(1)\) 查询.
代码
#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i<=last;++i)
using namespace std;
const int maxN=2333;
int N,M,T;
int arr[maxN][maxN];
int sum[maxN][maxN];
//对于竖着的边和横着的边需要不同处理
int suml[maxN][maxN];
int sumr[maxN][maxN];
int main()
{
scanf("%d%d%d",&N,&M,&T);
char ch;
REP(i,1,N)
{
REP(j,1,M)
{
cin>>ch;
arr[i][j]=ch-'0';
sum[i][j]=arr[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];//二维前缀,记录1出现的次数
if(arr[i][j])
{
if(arr[i-1][j])//如果有竖着的边
{
suml[i][j]++;
}
if(arr[i][j-1])//如果有横着的边
{
sumr[i][j]++;
}
}
//处理二维前缀
suml[i][j]+=suml[i-1][j]+suml[i][j-1]-suml[i-1][j-1];
sumr[i][j]+=sumr[i-1][j]+sumr[i][j-1]-sumr[i-1][j-1];
}
}
int fx,fy,lx,ly,answer;
REP(i,1,T)
{
scanf("%d%d%d%d",&fx,&fy,&lx,&ly);
//按照公式计算
answer=sum[lx][ly]-sum[lx][fy-1]-sum[fx-1][ly]+sum[fx-1][fy-1];
//竖着的的边的数量与横着的边的数量计算方式略有不同,如果不能理解可以画一张图来帮助理解
answer-=suml[lx][ly]+suml[fx][fy-1]-suml[lx][fy-1]-suml[fx][ly];
answer-=sumr[lx][ly]+sumr[fx-1][fy]-sumr[lx][fy]-sumr[fx-1][ly];
printf("%d\n",answer);
}
return 0;
}