题解:P13787 地毯 加强版

· · 题解

题目链接:P13787 地毯 加强版

题目大意

给你一个有 n\times n 个格子的坐标,再在上面铺 m 块地毯,问你每个位置的行数加列数异或被多少个地毯覆盖的和。

做法1:暴力

我们利用一个二维数组来记录每个点被多少个地毯覆盖,最后按要求输出。可是那样的话,时间复杂度度太高了,有没有更简单且高效的方法呢?

有的兄弟,有的。想修改区间值的话 二维差分 是一个十分简单的方法。

正解:二维前缀和 + 二维差分

相关知识:一维前缀和 + 一维差分

一维前缀和

假如给你一个数组,你有没有一种可以用 O(1) 的时间复杂度求出其中的任意一个区间和的方法呢?一维前缀和 是一种可以只用时间复杂度为 O(n) 预处理就可以用 O(1) 的时间复杂度求出任意区间和的方法。它的原理简单易懂:

我们将数组中第 i 个数变成前 i 个数的总和,这样就完成预处理啦!

所以现在如何求区间和呢?假如有 5 个数:

1 2 3 4 5

预处理后 5 个数为:

1 3 6 10 15

而如果我们要求区间 [2,4] 的总和,只需要用预处理后的第 4 个数减去第 1 个数就可以了!

为什么呢?其实原理非常简单:预处理后的第 4 个数为前 4 个数的总和,第 1 个数为前 1 个数的总和。两者相减,不就是第 2 个数至第 4 个数的总和吗?

可是如果我们还要先更改区间值又该如何呢?

一维差分 可以帮你解决此问题:

一维差分

同样 5 个数:

1 2 3 4 5

我们要将区间 [2,4] 中的每一个数都加上 1 该如何做呢?

一维差分 同样需要先用时间复杂度为 O(n) 的预处理,使原数组的第 i 个数为前 i 个数的总和: 预处理后 5 个数为:

1 1 1 1 1

也就是说,本来的数组为现在预处理后的数组前缀和数组!

现在,如果我们将第 2 个数加上 1 的话,本来的数组会有什么变化呢?

1 3 4 5 6

不难发现,从第 2 个数开始,每个数都加了 1

聪明的人已经看出,现在我们只要将第 5 个数加上 -1,就完成了将区间 [2,4] 中的每一个数都加上 1 的操作。

原因是我们通过反向利用 一维前缀和 ,来高效修改了区间值。

二维前缀和

懂了 一维前缀和 的相信很快也能理解 二维前缀和

二维前缀和 其实也很好推,这里不举例了。

假如有一个二维数组,我们要求 (1,1)(x,y) 的矩阵中的所有数的和。

现在设 a[i][j] 为第 i 行第 j 列的数,s[i][j](1,1)(i,j) 的矩阵中的所有数的和,那么:

利用容斥原理可以理解上式。 ### 二维差分 **二维差分** 其实也同 **一维差分**一样,是“反向前缀和”。 同样 **一维差分**一样需要先用时间复杂度为 $O(n^2)$ 的预处理。设预处理后的数组为 $c$。 如果我们要将一个二维数组的 $(x,y)$ 至 $(z,s)$ 矩阵中的每一个数加 $1$ 只需 $4$ 步: ```cpp c[x][y]++; c[x][s+1]--; c[z+1][y]--; c[z+1][s+1]++; ``` 现在都知道该如何做了吧! # 代码 ```cpp #include <bits/stdc++.h> using namespace std; template<typename T>inline void read(T&x){ x=0;char c;int sign=1; do{c=getchar_unlocked();if(c=='-') sign=-1;} while(!isdigit(c)); do{x=x*10+c-'0';c=getchar_unlocked();}while(isdigit(c)); x*=sign; } int n,m; int c[5050][5050]; int x,y,z,s; long long ans; int main(){ read(n),read(m); for(int i=1;i<=m;i++){ read(x),read(y),read(z),read(s); c[x][y]++; c[x][s+1]--; c[z+1][y]--; c[z+1][s+1]++; //差分处理 } for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ c[i][j]=c[i-1][j]+c[i][j-1]-c[i-1][j-1]+c[i][j];//前缀和处理 ans+=(i+j)^c[i][j];//计算答案 } } cout<<ans; return 0; } ``` 这道题跟[P3397 地毯](https://www.luogu.com.cn/problem/P3397) 题干完全一样,只是数据增强与输出不同。可以复习巩固关于 **前缀和** 与 **差分** 的基础知识,也值得一做。