题目大意:
给定一N*N的矩阵,每个元素为1或0,初始全为0,有如下两种操作
(1)将某一个矩形区域内的所有元素取反,即1改为0,0改为1
(2)询问某一个元素的值,输出0或1
首先不难看出,因为只有01所以与其取反,不如给区间中的某个元素都+1,和开关灯问题的性质相同,最后模2即可;
查询某个点的当前的值,可以转化为查询当前所有的操作中有多少个操作区间包含所查询点(因为每个操作都是给区间中的每个元素+1嘛);
一个区间可以用对角线上的两个顶点表示,如果一个点在这个区间中,那么从矩阵(0,0)位置到该点做组成的矩阵一定包含且只包含一个顶点;
但是这样的话无法区分矩阵外面的点,如果用四个顶点表示这个矩阵呢?这时,矩阵内点所围的矩阵包含一个顶点,而矩阵外的点所围的矩阵包含0、2或4个顶点,模2恰好为0,这样就能通过从(0,0)到某点矩阵所包含的顶点数来完成矩阵内外点的区分了
再由叠加的性质,通过计算顶点数的和可以在模2的意义下区分查询点点被多少个矩阵所围
于是问题就转化为了:以(0,0)和所询问点(x,y)所围成的矩形中包含多少顶点,即子矩阵矩阵元素和是多少?每插入一个矩形,就将其四个顶点插入树状数组,查询时,即求给定区间内的元素和,解决~
关于树状数组:
树状数组是什么?树状数组虽然描述起来是数组,但是表示其元素的关系上应该用类似树的形式画出
树状数组能做什么?最常用的目的:求某个区间的和,给定一个数组若求一段区间的和,我们可以通过预处理出每个“前缀”的和在查询时得到O(1)的复杂度,然而如果这个数组是动态的话,比如我们可以修改某个元素的值,再查询时如果不做预处理,修改O(1),查询需要整体求和O(n);如果预处理,查询O(n),修改需要修改所有包含这个元素的值O(n)。而树状数组就是在这两种状况的一个平衡,它所做的是“不完全”的预处理,将修改的一部分代价转移到查寻时来做,这样查寻和修改都是O(logn),效率较高
而树状数组扩展到高维也很容易,它不过是一个求和的预处理而已
顺便越发的觉得树状数组这个结构十分优美了,虽然所能做的比较有限,但是实现起来相当简单
下面是这道题的代码,注释不多,希望上面的文字描述大概就够了....
#include <cstdio>
#include <cstring>
#define MAXN 1010
int c[MAXN][MAXN];
int lowbit(int k)
{
return k & -k;
}
void modify(int a, int b, int d)
{
for (int i = a; i < MAXN; i += lowbit(i))
{
for (int j = b; j < MAXN; j += lowbit(j))
{
c[i][j] += d;
}
}
}
int check(int a, int b)
{
int total = 0;
for (int i = a; i > 0; i -= lowbit(i))
{
for (int j = b; j > 0; j -= lowbit(j))
{
total += c[i][j];
}
}
return total;
}
int main()
{
int caseSet, n , t;
char str[3];
int x1, y1, x2, y2;
int tmp;
scanf("%d", &caseSet);
while (caseSet--)
{
scanf("%d %d", &n, &t);
memset(c, 0, sizeof(c));
while (t--)
{
scanf("%s", str);
switch (str[0])
{
case 'C':
scanf("%d%d%d%d", &x1, &y1, &x2, &y2);
x2++; y2++; // 这里注意顶点的设置应类似“左闭右开”的形式,否则查询边上的点的时候可能会出问题
modify(x2, y2, 1);
modify(x1, y2, 1);
modify(x2, y1, 1);
modify(x1, y1, 1);
break;
case 'Q':
scanf("%d%d", &x1, &y1);
tmp = check(x1, y1);
printf("%d\n", tmp % 2);
break;
}
}
printf("\n");
}
return 0;
}