题解 P5542 【[USACO19FEB]Painting The Barn】

· · 题解

看了一下题解,没有讲的非常详细的,第一次接触二维差分不画图的话挺难理解的

所以我来一发详细一点的题解,给像我一样的蒟蒻一点温暖

废话不多,进入正题

1. 二维前缀和 (这里引用一下别人的图,实现画不出来)

首先我们学习二维差分的之前需要先了解二维前缀和

如图:

因为是从左到右,从上到下的遍历,当要求红色部分,(0,0) 到(i, j)处的前缀和时,我们黄色部分和蓝色部分已经是已知的了,而它们重叠的部分就是绿色部分,所以把黄色和蓝色部分的结果加起来,再减去绿色部分,最后加上 (i, j) 处的值就是(i, j)位置的前缀和了。

所以,二维前缀和就是:

sum[i][j]=a[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]

而我们要求左上角是(x1,y1),右下角是(x2,y2)的矩形区间内的值处理出前缀和后也可以O(1)时间内求出来。

如图:

我们要求紫色部分的值,我们已知的是黄色部分的值,但它多了两个蓝色部分的值,而两个蓝色部分有重叠了个绿色部分

所以要求的区间内的值就是:

sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][x2-1]

2. 二维差分

(下面是自己画的图,画的不好,将就一下)

我们以这题的样例来解释什么是二维差分

首先给出结论:

若想在点(x1,y1)到点(x2,y2)围成的矩形的每个位置增加1
则我们要进行以下操作:

设vis[i][j]:原点到点(i,j)围成的矩形的总和
    vis[x1 + 1][y1 + 1]++;
    vis[x2 + 1][y2 + 1]++;
    vis[x1 + 1][y2 + 1]--;
    vis[x2 + 1][y1 + 1]--;

我们给出根据样例的三组数据实现后的坐标轴:

黑色线段围成的矩形就是每次要加1的矩形

下面我们解释为什么要这样做:

我们就以 点(1,1) 和 点(5,5)为例讲解

首先 vis[2][2]++;

而 vis[2][2] 的值改变,谁的值会受影响呢?

很明显: i >= 2 && j >= 2的vis[i][j]的值全部都会收到影响

我们先来看 i = 2,j >= 2的情况  也就是点(1,1)到点(2,6)橙色长方形

我们令 vis[2][6]--  进行此操作后 我们会发现 
vis[2][j >= 5]的值又不会受到影响了

很简单,我们来证明一下: 令 j=7

     想象一下点(0,0)和点(2,7)围成的矩形 
     我们会发现 点(2,2)加的1 和点(2,6)减的1 抵消了
     故不会受影响了
     证毕

这里明白后其他两处加1和减1就也能明白了

不明白的话多看几遍,深度理解

而之后的操作我在这里也给出:

int ans = 0;
for(int i = 1; i <= 1000; i++) {
   for(int j = 1; j <= 1000; j++) {
        vis[i][j] += vis[i - 1][j] + vis[i][j - 1] - vis[i - 1][j - 1];
        if(vis[i][j] == k) ans++;
   }
}
cout << ans << endl;

然后我给出最终 vis 数组中的值,方便加深理解

最后,给出本题的AC代码:

#include <iostream>
#include <cstring>

using namespace std;

int n, k;
int vis[1005][1005];

int main() {
    cin >> n >> k;
    int x1, y1, x2, y2;
    for(int i = 1; i <= n; i++) {
        cin >> x1 >> y1 >> x2 >> y2;
        vis[x1 + 1][y1 + 1]++;
        vis[x2 + 1][y2 + 1]++;
        vis[x1 + 1][y2 + 1]--;
        vis[x2 + 1][y1 + 1]--;
    }
    int ans = 0;
    for(int i = 1; i <= 1000; i++) {
        for(int j = 1; j <= 1000; j++) {
            vis[i][j] += vis[i - 1][j] + vis[i][j - 1] - vis[i - 1][j - 1];
            if(vis[i][j] == k) ans++;
        }
    }
    cout << ans << endl;
    return 0;
}