【二维差分】洛谷P3997地毯
群里的大佬都去Online#3的提高组了,就蒟蒻(我)一个人报了普及组,做做前缀和与差分的题聊以慰籍
题目传送门
前言
这道题在洛谷上wxz大佬的题解已经说得很清楚了(自己给自己出的题写题解),虽然题目说难度在Day2T1,但是那个1e3的数据范围刚好可以枚举,所以各种玄学暴力做法都能过。蒟蒻把自己做这道题的思路还有对差分的一些(浅薄的)理解整理下来,有不当之处欢迎各位大佬评论指出~
解题思路
这道题本质上可以这样理解:
给定一个矩阵,每个位置初始是0,现在给定若干个可能重叠的子矩阵,每个子矩阵的覆盖区域加上1,最后输出整个矩阵。
我们把每个子矩阵拆成若干行,然后一个子矩阵就转换为若干个长度相同的子段,也就转化成了一维的差分。最后扫描一遍,根据标记输出就行了。
上面是大概的思路,下面本蒟蒻会从一维差分梳理到这道题的代码,各位大佬可以直接去看最下面的代码。
一维差分
想要做差分的题,先要理解差分到底有什么用,最常见的差分题目就是:
给定一个序列,初始化为0,然后对其进行m次操作:
每次操作给出该序列的一个子段[L,R],这个子段内的所有元素+1
最后要求你输出整个序列。
没有学过差分的话,看到题目可能会对每一个[L,R],遍历它,然后每个元素都+1。虽然可以得到答案,但是复杂度是O(n*m),要是n是10e6,10e7,基本就妥妥的TLE了。这个时候就要用到差分的思想:在区间的首尾打上标记,输出的时候根据标记输出对应的值就得到最终答案,这里举个例子让大家更好的明白:
假设序列长度为10,3次操作给定区间是[2,8],[4,6],[1,3]。
第一次我们在2处打上标记(+1),9处打上标记(-1),在9处打上标记是因为8还属于这个子段,到9才离开。数组为:
0 1 0 0 0 0 0 0 -1 0
第二次在4处打一个标记,7处也打一个
0 1 0 1 0 0 -1 0 -1 0
第三次在1和4处打上标记:
1 1 0 0 0 0 -1 0 -1 0
如果我们不在末尾打上标记,就不知道这个子段到哪里结束,有人说:那把子段每一个元素都打上标记不就知道了?可以,但是那就变成了暴力枚举了,没有任何优化。现在标记是打好了,但显然这不是最后的答案,差分还有一个重点是如何输出,差分的输出的思想是这样的:记录走到现在的标记之和,输出这个和。不理解?我们来模拟一下那个例子的输出:
走到1,标记和是1吧,那就输出1
到2,标记和是2,那就输出2
在3,4,5,6,标记和都没变动,还是2
到了7,标记和减1,输出1
在8这里,还是1
到了9,又减了1,输出0
到了10,还是0
输出为1 2 2 2 2 2 1 1 0 0,各位可以手算一下,这确实是正确的输出。个人认为,标记和其实就是你这个位置有几个子段和覆盖,当你走出一个子段,那这个位置就少了一个子段覆盖,所以加上-1,代表少了一个子段覆盖这个位置(当然也可能是-2,那说明两个子段都在这个位置结束,以此类推)。
一维差分就讲到这里了,如果大家还不理解可以去luogu看看wxz大佬的题解,应该就会懂了。那么怎么把一维差分用到这道题呢?刚开始说过,可以把一个子矩阵拆成一行一行的,不就是几个长度相同的子段吗(只有所在行数不同),然后对于每个子段,用刚才一维差分思想打标记就行了。每个子矩阵要花费O(m)(m是行数)的时间,那么总时间复杂度就是O(n*m)(n是子矩阵数)
最后,贴上这题的AC代码(这篇可能有点长,建议多读几遍,差分那里可以手动模拟一下(其实是我自己讲复杂了?):
#include<iostream>
#include<cstdio>
using namespace std;
int n,m;
int matrix[1100][1100];
int main(){
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 1;i <= m;i++){
int sx,sy,ex,ey;
cin >> sx >> sy >> ex >> ey;
//遍历每一行
for(int i = sx;i <= ex;i++){
//在sy和ey+1处分别打上标记
matrix[i][sy]++;
matrix[i][ey+1]--;
}
}
//扫描数组
for(int i = 1;i <= n;i++){
int sum = 0;
for(int j = 1;j <= n;j++){
sum += matrix[i][j];
printf("%d ",sum);
}
printf("\n");
}
return 0;
}
写题解不容易,求赞
(写完刚好普及组要开始了,蒟蒻要去被虐了 /捂脸)