[二维树状数组] 情书密码 题解

题目描述

有消息称:绝恋找到了自己的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;
}

后记:其实树状数组可以存很多种类的数,一般题目中不会给出,需要自己找;
当发现树状数组并不能完全存完我们想存的数时,不妨加几维,可能就可以解决;

posted @ 2024-02-17 11:46  Peppa_Even_Pig  阅读(133)  评论(7编辑  收藏  举报