题目
题目描述
有一个 n 个元素的数组,每个元素初始均为 0。有 m 条指令,要么让其中一段连续序列数字反转——0 变 1,1 变 0(操作 1),要么询问某个元素的值(操作 2)。 例如当 n = 20 时,10 条指令如下:
输入格式
第一行包含两个整数 n, m,表示数组的长度和指令的条数; 以下 m 行,每行的第一个数 t 表示操作的种类:
若 t = 1,则接下来有两个数 L, R,表示区间 [L, R] 的每个数均反转; 若 t = 2,则接下来只有一个数 i,表示询问的下标。
输出格式
每个操作 2 输出一行(非 0 即 1),表示每次操作 2 的回答。
样例
输入
20 10
1 1 10
2 6
2 12
1 5 12
2 6
2 15
1 6 16
1 11 17
2 12
2 6
输出
1
0
0
0
1
1
说明
时限:1s
对于 50% 的数据,1 ≤ n ≤ 1e3,1 ≤ m ≤ 1e4;
对于 100% 的数据,1 ≤ n ≤ 1e5,1 ≤ m ≤ 5 × 1e5 ,保证 L ≤ R。
思路
树状数组
- 因为涉及到区间修改,如果遍历的话O(n*m)直接TLE。所以我们可以想到用树状数组将区间修改操作缩小到O(logn),这样总的复杂度为O(m*logn)是可以ac的。
- 简单介绍下树状数组:物理结构为数组,逻辑结构为二维的树。c[i]维护的是包括c[i]在内后面的lowbit(i)个元素。从i往前遍历到0,不再是按照常规的步长为1来遍历,而是按照步长
i-=lowbit(i)
进行遍历,此时也就是按照二进制的位数来遍历。这样遍历是完备的,没有重复元素,没有缺少元素。 - 因此能够将对区间
(n,m]
查询和修改的复杂度从O(m-n)
变为O(logm-logn)
差分
- 使用树状数组的关键在于确定好差分的含义,也就是区间的值代表的含义是什么。
- 对于刚做完树状数组前缀和的题的我,我还是坚持当初的想法,认为
c[x]
维护的区间是1的个数的和。这会导致我在进行修改操作时的复杂度上升为O(nlogn),所以理所当然的TLE了一般的测试点。
错误代码
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int c[N];
void debug(int n){
cout<<"树状数组: ";
for(int i=1;i<=n;i++){
cout<<c[i]<<' ';
}
cout<<'\n';
}
int lowbit(int x){
return x&(-x);
}
//不大于i的位置的个数
int get_num(int i){
int num=0;
for(;i>0;i-=lowbit(i)) num+=c[i];
return num;
}
int get(int i){
return get_num(i)-get_num(i-1);
}
void update(int l,int r,int n){
int old,i;
for(int k=l;k<=r;k++){
old=get(k);
i=k;
if(old==0){
for(;i<=n;i+=lowbit(i)) c[i]++;
}
else{
for(;i<=n;i+=lowbit(i)) c[i]--;
}
}
}
int main(){
int n,m;
cin>>n>>m;
int flag,x,y;
for(int i=0;i<m;i++){
cin>>flag;
if(flag==2){
cin>>x;
cout<<get(x)<<'\n';
}
else{
cin>>x>>y;
update(x,y,n);
// debug(n);
}
}
}
- 而正确差分思路是,按照位反转的思路去解题,c[i]记录的是维护区间的值反转的次数。
update(x)
就是将小于等于x的数字全部进行反转,这样更新区间[l,r]
的时候只需要进行update(l-1)
和update(r)
就可以了,这样复杂度仍为O(logn)
AC代码
#include<bits/stdc++.h>
#define N 100005
using namespace std;
int c[N];
void debug(int n){
cout<<"树状数组: ";
for(int i=1;i<=n;i++){
cout<<c[i]<<' ';
}
cout<<'\n';
}
int lowbit(int x){
return x&(-x);
}
//看这个数被反转了多少次就行
int get(int x,int n){
int sum = 0;
for(;x<=n;x+=lowbit(x)) sum+=c[x];
return sum;
}
//将小于等于x位置的数反转
void update(int x){
for(x;x>0;x-=lowbit(x)) c[x]++;
}
int main(){
int n,m;
cin>>n>>m;
int flag,x,y;
for(int i=0;i<m;i++){
cin>>flag;
if(flag==2){
cin>>x;
cout<<get(x,n)%2<<'\n';
}
else{
cin>>x>>y;
update(y);
update(x-1);
// debug(n);
}
}
}