题目描述
有消息称:绝恋找到了自己的Miss Right,正准备自己的表白。绝恋已经写好了情书,但为了避免其它人截获,他对情书进行加密。
为了打探绝恋的私密,你冒着生命危险终于搞到了这封情书。原以为可以轻易将情书解密,结果竟然发现聪明的绝恋并没有直接写出加密用的密码,而是在那粉红色的信纸背面写着“T=你的幸运数字”。
就这么放弃了吗?不,作为一个高智商的OIer,你决不轻言放弃。你还搞到了绝恋做密码时的草稿,通过一定的分析,你发现草稿中隐藏了绝恋的密码,具体规则如下:
草稿纸上写着一个N*M 的矩阵,每个位置都有一个数字C,绝恋对该矩阵不断进行操作,有时会修改某一位置的值,还不时计算出一个矩形内特定数字的个数作为密码的一部分,绝恋十分聪明,他进行了许多次这样的操作,因此密码也异常复杂,但是你已经下定决心要算出密码了,所以你一定要算出来!!
输入格式
第一行有两个数字N,M
接下来N 行,每行M 个数,第i+1 行第j 个数表示格子(i,j)的初始值。
接下来一个整数Q
接下来Q 行,每行描述一个操作
操作1:1 x y c,表示将格子(X,Y)的权值改为C
操作2:2 x1, x2, y1, y2,表示询问矩形内有多少个位置的权值为C。x1, x2分别为矩形横坐标中的最小值和最大值,y1, y2为矩形纵坐标中的最小值和最大值。
输出格式
对于每一个操作2,输出一个整数表示答案,每数一行。
样例
样例输入
3 3
1 2 3
3 2 1
2 1 3
3
2 1 2 1 2 1
1 2 3 2
2 2 3 2 3 2
样例输出
1
2
题解
拿到题,发现是在矩阵上进行单点修改和区间查询,很容易想到二维树状数组;
二维树状数组的概念和板子在这里不在赘述,这道题和板子最大的区别在于:1、它不是加上一个数,而是修改一个数;2、它求的不是前缀和,而是点出现的次数;
第一个实现简单,直接在单点修改时加上俩数差值即可;
但第二个如果要实现的话,需要维护一个差分数组,并在每一个区间遍历找每一个点判断是否合法;
貌似还不如打暴力(其实上述做法就是暴力);
当你TLE收获30分时,只能老老实实再换思路;
做DP的题时,相信大家都明白了维数的重要性,多开几维没坏处,有时候多开几维就能存储全我们想要的信息,而且大多数情况下不会MLE;
那本题,我们发现难点在于如何存储在一个区间内一个数的出现次数;
受DP的启发,我们可以将树状数组多开一维;
定义树状数组t[i][j][k]表示从(1, 1)到(i, j)这段区间,k这个数出现的次数(这确实很像DP);
当我们修改某个点的值时,只需将t[i][j][原来的值]--,将t[i][j][现在的值]++;
当我们查询某个区间某个数的出现次数时,只需查询树状数组在这一区间内的前缀和即可;
这样这个题就解决了;
代码
#include <iostream>
using namespace std;
int n, m, q;
int a[305][305];
int t[305][305][305]; //存储矩阵i,j中出现k的次数;
int lowbit(int x) {
return x & (-x);
}
void add_dian(int x, int y, int past, int now) {
for (int i = x; i <= n; i += lowbit(i)) {
for (int j = y; j <= m; j += lowbit(j)) {
t[i][j][past]--;
t[i][j][now]++;
if (t[i][j][past] < 0) t[i][j][past] = 0; //如果减超了,就赋值为0(主要应用于当past == 0时)(不写也行);
}
}
}
int ask_sum(int x, int y, int c) {
int ans = 0;
for (int i = x; i > 0; i -= lowbit(i)) {
for (int j = y; j > 0; j -= lowbit(j)) {
ans += t[i][j][c];
}
}
return ans;
}
int main() {
cin >> n >> m; //初始化一个n * m的全零矩阵;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
cin >> a[i][j];
add_dian(i, j, 0, a[i][j]); //将0替换成a[i][j];
}
}
cin >> q;
int k, x, y, c, x1, y1;
for (int i = 1; i <= q; i++) {
cin >> k;
if (k == 1) {
cin >> x >> y >> c;
add_dian(x, y, a[x][y], c);
a[x][y] = c; //注意这里,原矩阵也要被更新,否则等再次更新此处时past的值会不对;
}
if (k == 2) {
cin >> x1 >> x >> y1 >> y >> c;
int ans = ask_sum(x, y, c) - ask_sum(x, y1 - 1, c) - ask_sum(x1 - 1, y, c) + ask_sum(x1 - 1, y1 - 1, c); //二维矩阵前缀和;
cout << ans << endl;
}
}
return 0;
}
后记:其实树状数组可以存很多种类的数,一般题目中不会给出,需要自己找;
当发现树状数组并不能完全存完我们想存的数时,不妨加几维,可能就可以解决;