序列「主席树」
题目描述
小 A 把自己之前得到的序列展示给了小 B,不过这一次,他并不要求小B模仿他之前的行为。他给了小 B 一些询问,每个询问都是 \(l,r,x\) 的形式,要求小 B 数出在序列的第 \(l\) 个到第 \(r\) 个元素中有多少是不小于 \(x\) 的。小 B 很快就算出来了。小 A 很不甘心,于是要求动态修改这个序列......这样,他只要求每次修改后求出所有询问答案的和即可。然而小 B 还是很快就算出来了,小 A 很生气,于是把问题抛给了你。
输入格式
由于一些原因,本题采取一定的方式加密了修改操作。
第一行三个整数 \(n,m,q\),分别表示序列长度、询问个数和修改次数。第二行 \(n\) 个正整数描述序列。接下来 \(m\) 行每行三个数 \(l,r,x\) ,表示一次询问。最后 \(q\) 行每行两个数 \(p,v\) ,表示把 \(p\) ^ \(lastans\) 这个位置上的数修改成 \(v\) ^ \(lastans\)(其中 \(lastans\) 指上次修改之后的答案,初始即为没有修改过的原序列的询问答案,^为异或符号,\(C/C++\) 中为^,\(pascal\) 中为 \(xor\))。
输出格式
\(q+1\) 行每行一个整数,第一行表示原序列的所有询问答案之和,后面 \(q\) 行表示每次修改之后的序列的所有询问答案之和。
样例
样例输入
4 2 2
1 4 2 3
2 4 3
1 3 2
6 6
2 7
样例输出
4
3
4
数据范围与提示
对于 \(20\%\) 的数据,\(n,m,q⩽100\)
对于 \(40\%\) 的数据,\(n,m,q⩽1,000\)
对于 \(100\%\) 的数据,\(n,m,q⩽100,000\),序列中的数(包括修改后的)均为正数且不超过 \(n\),保证数据合法。
思路分析
- 考虑如何快速统计答案。对于序列中的每个元素,通过计算出它在哪几个区间里,并且满足区间的要求,就可以对答案产生贡献
- 所以可以对每一个元素都开一个主席树,每次询问都只需要对左右端点进行标记(和差分有一丢丢类似),即 \(l\) 的主席树中 \(x\) 的值\(+1\),\(r+1\) 的主席树中将 \(x\) 的值 \(-1\),这时候就可以查询一下第 \(i\) 棵主席树中 \(0\)~a\([i]\) 的区间和,即有多少个 \(x\) 小于等于 \(a[i]\),就是对答案产生的贡献
- 而这时用主席树的最大优点就是将前面的主席树复制了过来,保证前面的询问区间对后面也会奏效
- 修改操作很水,减去旧的贡献,再加上新的贡献就行
详见代码
\(Code\)
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define R register
#define N 100010
using namespace std;
inline int read(){
int x = 0,f = 1;
char ch = getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
int n,m,q,a[N],ans;
int root[N],tr[N<<10],ls[N<<10],rs[N<<10],cnt;
struct xzy{//结构体记录询问
int pos,mark;
xzy(){}
xzy(int _pos,int _mark){pos = _pos,mark = _mark;}
};
vector<xzy>pro[N];
void update(int &rt,int pre,int l,int r,int pos,int val){ //主席树板子
rt = ++cnt;
if(l==r){
tr[rt] = tr[pre]+val;
return;
}
int mid = (l+r)>>1;
if(pos<=mid){
update(ls[rt],ls[pre],l,mid,pos,val);
rs[rt] = rs[pre];
}else{
update(rs[rt],rs[pre],mid+1,r,pos,val);
ls[rt] = ls[pre];
}
tr[rt] = tr[ls[rt]] + tr[rs[rt]];
}
int query(int rt,int l,int r,int s,int t){
if(s<=l&&t>=r)return tr[rt];
int mid = (l+r)>>1;
int res = 0;
if(s<=mid)res += query(ls[rt],l,mid,s,t);
if(t>mid)res += query(rs[rt],mid+1,r,s,t);
return res;
}
int main(){
n = read(),m = read(),q = read();
for(R int i = 1;i <= n;i++)a[i] = read();
for(R int i = 1;i <= m;i++){
int l = read(),r = read(),x = read();
pro[l].push_back(xzy(x,1));//左右端点分开
pro[r+1].push_back(xzy(x,-1));
}
for(R int i = 1;i <= n;i++){
root[i] = root[i-1];
for(R int j = 0;j < pro[i].size();j++)update(root[i],root[i],0,n,pro[i][j].pos,pro[i][j].mark); //复制并更新主席树,1~i的区间才会产生贡献
ans += query(root[i],0,n,0,a[i]);//0~a[i]之间的点,即小于等于a[i]的值才会产生贡献
}
printf("%d\n",ans);
for(R int i = 1;i <= q;i++){
int p = read(),v = read();
int loc = p^ans,val = v^ans;
ans -= query(root[loc],0,n,0,a[loc]);//旧贡献
ans += query(root[loc],0,n,0,val);//新贡献
a[loc] = val;
printf("%d\n",ans);
}
return 0;
}