[Vijos P1448] [树状数组] 校门外的树 题解

题目描述

校门外有很多树,学校决定在某个时刻在某一段种上一种树,保证任一时刻不会出现两段相同种类的树,现有两种操作:

  • k == 1,读入l, r表示在 l 到 r 之间种上一种树,每次操作种的树的种类都不同;

  • k == 2,读入l, r表示询问 l 到 r 之间有多少种树。

注意:每个位置都可以重复种树。

输入格式

第一行 n 表示道路总长为 n,共有 m 个操作;
接下来 m 行为 m 个操作。

输出格式

对于每个 k == 2 输出一个答案。

样例

样例输入

5 4
1 1 3
2 2 5
1 2 4
2 3 5

样例输出

1
2

题解

拿到题第一眼,发现这不和HH的项链很像吗,但仔细观察就会发现,那道题是给出了确定的数列与确定的区间,而这道题的序列是变化的,这决定了区间询问不能由上一步继承下来,所以要换思路(也就是说,那道题是离线处理,这道题是在线处理);

仔细思考,依据树状数组求前缀和的思路,我们不难发现,如果要求一段区间的种类数,只需用1~右端点这段区间的总种类数-1~左端点这段区间的总种类数(类比树状数组的区间查询),即可得到解;

对于上述思路,很容易发现一个问题,当有一个种类跨越了区间左端点时,最后的结果会小1;

所以,我们应该用1~右端点这段区间的总种类数-1~左端点这段区间的总的完全种类数(不包括既在区间,又在区间前的种类,即半个种类),这样可以得到正确答案;

所以,这道题的本质是:求1~右端点这段区间的总种类数 和 1~左端点这段区间的总的完全种类数;

但种类在变化,如果用HH的项链这一题的思路是不可行的(因为要维护这一题的种类,既要改变树状数组的值,又要改变ma数组的值(ma数组详见HH的项链),而改变ma数组的值需要遍历),所以我们要找出一个在变化中只会影响树状数组的,或者说只用到树状数组来维护的几种值来作为替代;

所以,我们就想到维护左端点和右端点的数量,因为

1~右端点这段区间的总左端点数 相当于 1~右端点这段区间的总种类数;

1~左端点这段区间的总右端点数 相当于 1~左端点这段区间的总种类数;

依据上述思路,要求两种前缀和,于是用两个树状数组分别求左端点总数和右端点总数,最后用1~右端点这段区间的总左端点数 - 1~左端点这段区间的总右端点数(不包括左端点,因为种树是也会在左端点种)即为结果;

如果还不明白,那就画画图理解理解,我有点懒,就不画了;

所以,这道题的本质就是树状数组的单点修改与区间查询;

代码

#include <iostream>
using namespace std;
int t[1000005]; //左端点;
int t1[1000005]; //右端点;
int n, m;
int lowbit(int x) {
	return x & (-x);
}
void add_dian(int x, int k) { //左端点加点;
	while(x <= n) {
		t[x] += k;
		x += lowbit(x);
	}
}
void add_dian1(int x, int k) { //右端点加点;
	while(x <= n) {
		t1[x] += k;
		x += lowbit(x);
	}
}
long long ask_sum(int x) { //左端点求和;
	long long ans = 0;
	while(x > 0) {
		ans += t[x];
		x -= lowbit(x);
	}
	return ans;
}
long long ask_sum1(int x) { //右端点求和;
	long long ans = 0;
	while(x > 0) {
		ans += t1[x];
		x -= lowbit(x);
	}
	return ans;
}
int main() {
	cin >> n >> m;
	int k, a, b;
	for (int i = 1; i <= m; i++) {
		cin >> k >> a >> b;
		if (k == 1) {
			add_dian(a, 1);
			add_dian1(b, 1);
		}
		if (k == 2) {
			cout << ask_sum(b) - ask_sum1(a - 1) << endl; //不包括区间左端点,所以要a - 1;
		}
	}
	return 0;
}
posted @ 2024-02-16 17:20  Peppa_Even_Pig  阅读(124)  评论(4编辑  收藏  举报