链接:https://www.nowcoder.com/acm/contest/140/J
来源:牛客网
时间限制:C/C++ 4秒,其他语言8秒
空间限制:C/C++ 262144K,其他语言524288K
64bit IO Format: %lld
题目描述
White Rabbit has a rectangular farmland of n*m. In each of the grid there is a kind of plant. The plant in the j-th column of the i-th row belongs the a[i][j]-th type.
White Cloud wants to help White Rabbit fertilize plants, but the i-th plant can only adapt to the i-th fertilizer. If the j-th fertilizer is applied to the i-th plant (i!=j), the plant will immediately die.
Now White Cloud plans to apply fertilizers T times. In the i-th plan, White Cloud will use k[i]-th fertilizer to fertilize all the plants in a rectangle [x1[i]...x2[i]][y1[i]...y2[i]].
White rabbits wants to know how many plants would eventually die if they were to be fertilized according to the expected schedule of White Cloud.
输入描述:
The first line of input contains 3 integers n,m,T(n*m<=1000000,T<=1000000)
For the next n lines, each line contains m integers in range[1,n*m] denoting the type of plant in each grid.
For the next T lines, the i-th line contains 5 integers x1,y1,x2,y2,k(1<=x1<=x2<=n,1<=y1<=y2<=m,1<=k<=n*m)
输出描述:
Print an integer, denoting the number of plants which would die.
示例1
输入
2 2 2
1 2
2 3
1 1 2 2 2
2 1 2 1 1
输出
3
题目大意:
给出一个n*m的农场,每一个格子有一颗植物,每一颗植物有其对应的种类(用数字表示)。
现在要对农村撒肥料,而肥料也有用数字标记种类,当且仅当肥料的种类和植物的种类一致时,植物能存活,反之则死亡。
撒肥料的方式是给出矩形的左上角和右下角的坐标。
现在问在t次撒农药后,有多少颗植物死了。
题目分析:
首先这道题有两种思路,
一种是通过随机数+概率的做法,可以参考https://blog.csdn.net/weixin_41156591/article/details/81150890,感谢博主Fushicho_XF
另一种做法参考题解:
1.首先考虑特殊情况,种类只有0和1两种。
这种情况下,我们只需要对每一次操作进行区间更新(这里读者留意,更新方式有问题下面会阐述,这里还是请跟着笔者的思路)。
对于0的格子,如果查询结果非零则说明至少有1个1的肥料被撒到格子中;反之对于1的格子则是非T(操作数)则说明至少有1个0的肥料被撒到格子中。
2.考虑完特殊情况后,我们考虑普遍情况。
对于如果要把问题转变成上述的特殊情况的话,第一反应是二进制。
对于格子为i,而撒入肥料为j时,如果i!=j,那么说明两个数至少有一个二进制位不同。
这样想的话,我们对于每一个种类数,都可以通过枚举每一位二进制位把普遍情况变成特殊情况(即种类只有0和1),当特殊情况产生不符合的时候,那么这颗植物一定会死。通过枚举这种必要不充分条件就可以实现通过特殊情况枚举所有普遍情况。
3.但是这里会产生问题。
根据上面的思路,如果每一次都区间更新的话,即使是用lazy标记的线段树或者区间更新的树状数组都开销很大,在tle的边缘疯狂试探。
这里我们注意到一个问题,题目只在最后询问,那么我们是否可以通过lazy标记的思想实现减小开销呢?
当然是可以的
4.我们先看看一维的情况:
这里可以参考牛客小白月赛5 I.区间 (interval)
对于这种题目,我们可以考虑用标记+前缀和实现。
对于区间[L,R],如果我们用一个数组a作为增量标记,在[L,R]的区间更新V变为a[L]+=V,a[R+1]-=V
那么对a数组求前缀和后,[L,n]会因为“a[L]+=V”区间更新+V,而[R+1,n]会因”a[R+1]-=V"区间更新-V,这样就间接实现了区间更新。而这种方式只适合多次更新单次查询,但是无论是时间上还是空间上都有很大的节省。
对于二维的我们可以类比出,具体说明比较繁杂,读者可以自行在推导,或参考下面的代码。
5.然后是喜闻乐见的复杂度,因为要按位枚举因此需要log(nm)因此实际时间复杂度O(log(nm)*(nm+T))
可以看出还是吃得比较紧的,笔者尝试在赛后使用vector,在没有读入挂的情况下会被卡在70左右,因此还是推荐不要使用vector而直接用数组,使用方法见代码。
#include<bits/stdc++.h>
#define lson l,m
#define rson m+1,r
using namespace std;
typedef long long LL;
typedef unsigned long long ull;
const int maxn = 2 * int(1e6) + 100;
const int BN = 30;
const int inf = 0x3f3f3f3f;
const LL INF = 0x3f3f3f3f3f3f3f3f;
const int mod = (int)1e9 + 7;
const double eps = 1e-6;
struct points {
int x1, y1, x2, y2, type;
}query[maxn];
int n, m;
//二维坐标转一维,因为更新操作,因此把数据向上抬了一行
int getid(int x, int y) {
return (x + 1)*m + y;
}
int ones[maxn], zeros[maxn], die[maxn];
int num[maxn];
int main() {
//freopen("test.in","r",stdin);
int T;
scanf("%d%d%d", &n, &m, &T);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
scanf("%d", &num[getid(i, j)]);
for (int i = 1; i <= T; i++)
scanf("%d%d%d%d%d", &query[i].x1, &query[i].y1,
&query[i].x2, &query[i].y2, &query[i].type);
memset(zeros, 0, sizeof(zeros));
memset(ones, 0, sizeof(ones));
memset(die, 0, sizeof(die));
for (int pos = 0; pos <= 19; pos++) {
for (int i = 1; i <= T; i++) {
if ((query[i].type >> pos) & 1) {
ones[getid(query[i].x1, query[i].y1)]++;
ones[getid(query[i].x2 + 1, query[i].y1)]--;
ones[getid(query[i].x1, query[i].y2 + 1)]--;
ones[getid(query[i].x2 + 1, query[i].y2 + 1)]++;
}
else {
zeros[getid(query[i].x1, query[i].y1)]++;
zeros[getid(query[i].x2 + 1, query[i].y1)]--;
zeros[getid(query[i].x1, query[i].y2 + 1)]--;
zeros[getid(query[i].x2 + 1, query[i].y2 + 1)]++;
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
ones[getid(i, j)] += ones[getid(i - 1, j)] + ones[getid(i, j - 1)] - ones[getid(i - 1, j - 1)];
zeros[getid(i, j)] += zeros[getid(i - 1, j)] + zeros[getid(i, j - 1)] - zeros[getid(i - 1, j - 1)];
if ((num[getid(i, j)] >> pos) & 1) {
if (zeros[getid(i, j)])
die[getid(i, j)] = 1;
}
else {
if (ones[getid(i, j)])
die[getid(i, j)] = 1;
}
}
}
memset(zeros, 0, sizeof(zeros));
memset(ones, 0, sizeof(ones));
}
int ans = 0;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (die[getid(i, j)])
ans++;
printf("%d\n", ans);
return 0;
}