序列「主席树」

题目描述

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

posted @ 2020-09-23 17:42  HH_Halo  阅读(200)  评论(1编辑  收藏  举报