[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;
}