上链接:B - 题库 - 计蒜客
题意及分析
给定一个大小为N*N(N<10000)的方格阵,起初每一个格子为白色,现给定K次操作机会,对于每一次操作机会,给定区间[x1,x2]与[y1,y2],使所有坐标处于该区间内的格子颜色做一次翻转,求在完成K次操作后,为黑色的格子个数。
在VP这道题的时候,看到通过当时现场通过人数仅为2,果断选择先开别的题,最后回过来再看这道题的时候,发现其实很简单,其实就是二维差分的模板题,但题目存在一个坑点,我也是第一次遇到这样的坑法,我想这也是这道题过题很少的原因,不然应该是一道纯签到题。
进行若干翻转操作后为黑还是白可以简化成求当前格子被操作次数的奇偶性,若被操作了奇数次,当前格子必定为黑色,反之则为白色。
对于某个区间范围的数据集体做操作,暴力肯定会超时,故直接考虑差分,本题中为二维差分。
二维差分及前缀和
前缀和
定义原二维数组为a[N][N],与其对应的前缀和数组为s[N][N],则s[i][j]代表i行之前,j列之前(包括i行与j列)的所有元素之和。则有预处理公式
s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j]。
若已知前缀和数组,则查询(x1,y1)到(x2,y2)的所有元素之和表达式即为
s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]
差分
差分即为前缀和的逆运算,若已知数组为s[N][N],则之前讲的a[N][N]即为s[N][N]对应的差分数组。即差分数组a[N][N]求前缀和操作得到前缀和数组s[N][N],前缀和数组s[N][N]求差分操作得到差分数组a[N][N]。
则由前缀和数组得到差分数组的预处理公式
a[i][j]=s[i][j]-s[i-1][j]-s[i][j-1]+s[i-1][j-1];
区间加操作
对于差分与前缀和,用于多组较大规模的数据统一加减,可对原数组进行求差分操作得到差分数组,在差分数组上进行边界操作,再进行求前缀和操作,即可得到处理后的数组。
对于二维数组,若要在区间[x1,y1]到[x2,y2]之间的所有元素加上c,则需对其对应的差分数组做以下四步操作即可。
a[x1][y1]+=c; a[x1][y2+1]-=c; a[x2+1][y1]-=c; a[x2+1][y2+1]+=c;
本题中的应用
本题很明显的给出了做区间统一加的范围,即[x1,y1]到[x2,y2],但重点就在于数据规模达到了10000*10000,显然开两个二维数组有爆内存的风险,考虑到本题不存在初始数组,因为所有数组元素都是0,故可考虑直接在原差分数组的基础上做前缀和操作,得到最终所求的数组。公式即为
a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1]
但本题开10000*10000的int二维数组仍然爆内存了,但可以考虑到操作次数小于10000,即最终得到的前缀和数组元素值一定小于10000,故可尝试将int改为short,结果AC了。
代码如下
#include<bits/stdc++.h>
#define pb(x) push_back(x)
#define pii pair<int,int>
#define mk(x,y) make_pair(x,y)
using namespace std;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int N=10001;
int n,k;
short C[N][N];
int lowx,lowy,highx,highy;
void solve() {
cin>>n>>k;
memset(C,0,sizeof(C));
for(int i=1;i<=k;i++) {
cin>>lowx>>highx>>lowy>>highy;
C[lowx][lowy]++;
C[lowx][highy+1]--;
C[highx+1][lowy]--;
C[highx+1][highy+1]++;
}
C[0][0]=0;
int ans=0;
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];
if((C[i][j])%2==1) ans++;
}
}
cout<<ans<<endl;
}
int main() {
ios::sync_with_stdio(false);
int t;cin>>t;
while(t--)
solve();
}