各类数据结构(持续更新)

update:2021.6.30

并查集

程序自动分析

解释

把相同的直接加入到一个集合里面,然后再之后判断他们是否真正相同. 注意离散化。

模板

#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 10;
typedef pair<int,int> pii;

int fa[N];
int find(int x){
    if(x != fa[x]) fa[x] = find(fa[x]);
    return fa[x];
}

vector<int> alls;
vector<pii> a,b;

int findp(int x){
    return lower_bound(alls.begin(),alls.end(),x) - alls.begin();
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    int tt; cin>>tt;
    while(tt --){
        int n; cin>>n;
        alls.clear();a.clear();b.clear();
        bool plas = true;
        for(int i=1;i<=n;i++){
            int x,y,e; cin>>x>>y>>e;
            if(e) a.push_back(make_pair(x,y));
            else b.push_back(make_pair(x,y));
            alls.push_back(x);
            alls.push_back(y);
        }
        sort(alls.begin(),alls.end());
        alls.erase(unique(alls.begin(),alls.end()),alls.end());
        for(int i=0;i<=(int)alls.size();i++) fa[i] = i;
        for(auto [i,j] : a){
            i = find(findp(i)),j = find(findp(j));
            fa[i] = j;
        }
        for(auto [i,j] : b){
            i = find(findp(i)),j = find(findp(j));
            if(i == j){
                plas = false;
                break;
            }
        }
        if(!plas) cout<<"NO"<<endl;
        else cout<<"YES"<<endl;
    }
    
    return 0;
}

更多例题

银河英雄传说 维护距离的并查集。

关押罪犯 扩展域并查集 + 贪心,也可以用二分图 代码

树状数组

楼兰图腾

解释

维护当前这个点左边比它小(大)的,右边比它小(大)的,乘积之和就是答案。

模板

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;

#define lowbit(x) ((x) & (-x))
int c[N],a[N],A[N],V[N],n;

void update(int x,int y){
    for(int i=x;i<=n;i+=lowbit(i)) c[i] += y;
}

int ask(int x){
    int res = 0;
    for(int i=x;i;i-=lowbit(i)) res += c[i];
    return res;
}

signed main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=n;i>=1;i--){
        V[i] = ask(n) - ask(a[i]-1);
        A[i] = ask(a[i]-1);
        update(a[i],1);
    }
    memset(c,0,sizeof(c));
    int ansV = 0,ansA = 0;
    for(int i=1;i<=n;i++){
        ansV += V[i] * (ask(n) - ask(a[i]-1));
        ansA += A[i] * ask(a[i]-1);
        update(a[i],1);
    }
    cout<<ansV<<" "<<ansA<<endl;
    return 0;
}

更多例题

P1972洛谷 离线处理,按右端点排序,有一个性质,每个数的权值只与最右边出现的有关。代码

一个简单的整数问题 区间修改 + 单点查询,可用线段树懒标记。代码

一个简单的整数问题2 区间修改 + 区间查询。在前一题的基础上化简一个公式。代码

谜一样的牛 树状数组加二分。代码

线段树

常用知识点。 区间最值,区间求和,区间染色,矩形问题,区间k大数,二维线段树,三维线段树。

区间修改(加 & 乘)

线段树2

解释

多了一个乘法操作,可以考虑优先级。每次先算乘法。

首先,对于一个区间(和为s) 假设已经按 +a , 乘b进行了操作。值得到的值为( (s + a) * b )-> sb + ab 假设先乘得到(sb + a )这样相比,add应该还要再乘上一个b才对,所以,当更新到一个区间时,

为了进行先乘的操作而不让结果发生变化,应该将add乘上当前乘的值。 这个就是update里面更新乘法时候应该进行的操作。

push_down也是这样更新子区间的add,mul直接乘上。

初始状态mul为1,add为0。

模板

这题mod是输入的

#include<iostream>
#include<algorithm>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;

#define l(x) t[x].l
#define r(x) t[x].r
#define add(x) t[x].add
#define mul(x) t[x].mul
#define sum(x) t[x].sum

int n,m,mod;
int a[N];
struct SegmentTree{
	int l,r;
	ll add,mul=1,sum;
}t[N*4];

void push_up(int p){
	sum(p) = (sum(p*2) + sum(p*2+1)) % mod;
}

void push_down(int p){
	sum(p*2) = (sum(p*2) * mul(p) % mod + (r(p*2) - l(p*2) + 1) * add(p)) % mod;
	sum(p*2+1) = (sum(p*2+1) * mul(p) % mod + (r(p*2+1) - l(p*2+1) + 1) * add(p)) % mod;
	mul(p*2) = (mul(p) * mul(p*2)) % mod;
	mul(p*2+1) = (mul(p) * mul(p*2+1)) % mod;
	add(p*2) = (add(p*2) * mul(p) + add(p)) % mod;
	add(p*2+1) = (add(p*2+1) * mul(p) + add(p)) % mod;
	add(p) = 0;
	mul(p) = 1;
}

void build(int p,int l,int r){
	l(p) = l,r(p) = r;
	if(l == r){
		sum(p) = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	build(p*2,l,mid);build(p*2+1,mid+1,r);
	push_up(p);
}

void update(int p,int l,int r,int v,int id){
	if(l <= l(p) && r >= r(p)){
		if(!id){
			sum(p) = (sum(p) * v) % mod;
			add(p) = (add(p) * v) % mod;
			mul(p) = (mul(p) * v) % mod;
		}else{
			sum(p) = (sum(p) + (r(p) - l(p) + 1) * v) % mod;
			add(p) = (add(p) + v) % mod;
		}
		return ;
	}
	push_down(p);
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) update(p*2,l,r,v,id);
	if(r > mid) update(p*2+1,l,r,v,id);
	push_up(p);
}

ll ask(int p,int l,int r){
	if(l <= l(p) && r >= r(p)) return sum(p);
	push_down(p);
	ll val = 0;
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) val = (val + ask(p*2,l,r)) % mod;
	if(r > mid) val = (val + ask(p*2+1,l,r)) % mod;
	return val;
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m>>mod;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(1,1,n);
    while(m --){
    	int t; cin>>t;
    	if(t == 1){
    		int x,y,d; cin>>x>>y>>d;
    		update(1,x,y,d,0);
		}else if(t == 2){
			int x,y,d; cin>>x>>y>>d;
    		update(1,x,y,d,1);
		}else{
			int x,y; cin>>x>>y;
			cout<<ask(1,x,y)<<endl;
		}
	}
    
    return 0;
}

区间染色

例题地址

解释

另外的叫法 区间修改为同一个值

**注意区间离散化问题 ** ,在每个数后面都加一个数去填补空格区域。

懒标记法。

加一个color变量,表示当前区间是由哪个颜色染成,当为-1时,表示这个区间是混合颜色。可以继续往下分。

push_up : 子区间不一样,可以让父亲区间为-1,否则和子区间相同

push_down :当父区间为-1时,儿子区间不修改。否则修改为和父区间一样

ask :当一个区间颜色不为-1,则标记当前区间染的色,否则可以继续分解下去

其它部分就是标准模板

倒序插入

设置变量color表示当前区间是否全部被染色

倒序遍历,修改区间,当前区间修改的时候看是否能成功染色,成功染色的标志是存在一个子区间没有被完全染色。成功则计数

push_up :父亲区间是否被全部染色取决于两个儿子区间是否被全部染色

模板(懒标记法)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e4 + 10;
typedef long long ll;

#define l(x) t[x].l
#define r(x) t[x].r
#define color(x) t[x].color

int n,m;
int a[N],b[N];
vector<int> c;
bool vis[N];
struct SegmentTree{
	int l,r,color;
}t[N*4];

int find(int x){
	return lower_bound(c.begin(),c.end(),x)-c.begin();
}

void push_up(int p){
	if(color(p*2) == color(p*2+1)  && color(p*2) != -1) color(p) = color(p*2);
	else color(p) = -1;
}

void push_down(int p){
	if(color(p) != -1) color(p*2) = color(p*2+1) = color(p);
}

void build(int p,int l,int r){
	l(p) = l,r(p) = r;
	if(l == r) return ;
	int mid = (l + r) >> 1;
	build(p*2,l,mid);build(p*2+1,mid+1,r);
}

void update(int p,int l,int r,int id){
	if(l <= l(p) && r >= r(p)){
		color(p) = id;
		return ;
	}
	push_down(p);
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) update(p*2,l,r,id);
	if(r > mid) update(p*2+1,l,r,id);
	push_up(p);
}

int ask(int p,int l,int r){
	if(color(p) != -1){
		if(!vis[color(p)]){
			vis[color(p)] = true;
			return 1;
		}
		return 0;
	}
	push_down(p);
	int mid = (l(p) + r(p)) >> 1,val = 0;
	if(l <= mid) val += ask(p*2,l,r);
	if(r > mid) val += ask(p*2+1,l,r);
	return val;
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
    	cin>>a[i]>>b[i];
    	c.push_back(a[i]);
    	c.push_back(a[i]+1);
    	c.push_back(b[i]);
    	c.push_back(b[i]+1);
	}
    sort(c.begin(),c.end());
    c.erase(unique(c.begin(),c.end()),c.end());
    for(int i=1;i<=m;i++) a[i] = find(a[i])+1,b[i] = find(b[i])+1;
    build(1,1,(int)c.size());
    for(int i=1;i<=m;i++) update(1,a[i],b[i],i);
    vis[0] = true;
    cout<<ask(1,1,(int)c.size())<<endl;
    return 0;
}

模板(倒序插入维护区间染色)

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 1e4 + 10;
typedef long long ll;

#define l(x) t[x].l
#define r(x) t[x].r
#define color(x) t[x].color

int n,m;
int a[N],b[N];  
vector<int> c;
struct SegmentTree{
	int l,r,color;
}t[N*4];

int find(int x){
	return lower_bound(c.begin(),c.end(),x)-c.begin();
}

void push_up(int p){
	color(p) = color(p*2) & color(p*2+1);
}

void build(int p,int l,int r){
	l(p) = l,r(p) = r;
	if(l == r) return ;
	int mid = (l + r) >> 1;
	build(p*2,l,mid);build(p*2+1,mid+1,r);
}

int update(int p,int l,int r){
	if(color(p)) return 0;
	if(l <= l(p) && r >= r(p)) return color(p) = 1;
	int mid = (l(p) + r(p)) >> 1,val = 0;
	if(l <= mid) val |= update(p*2,l,r);
	if(r > mid) val |= update(p*2+1,l,r);
	push_up(p);
	return val;
}

int main(){
    ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++){
    	cin>>a[i]>>b[i];
    	c.push_back(a[i]);
    	c.push_back(a[i]+1);
    	c.push_back(b[i]);
    	c.push_back(b[i]+1);
	}
    sort(c.begin(),c.end());
    c.erase(unique(c.begin(),c.end()),c.end());
    for(int i=1;i<=m;i++) a[i] = find(a[i])+1,b[i] = find(b[i])+1;
    build(1,1,(int)c.size()+10);
    int ans = 0;
    for(int i=m;i>=1;i--) if(update(1,a[i],b[i])) ans ++;
    cout<<ans<<endl;
    return 0;
}

更多例题

数学计算 (就是标准单点修改,区间查询,维护区间乘积)

小白逛公园 (区间维护连续子序列的最大和)

无聊的序列 (等差数列,维护差分)

HDU2795 直接维护区间最大值就行了

区间最大公约数 要用到更相减损术,维护差分序列,并且用树状数组维护原序列前缀和。

扫描线

参考博客:

博客 按x或者按y扫描都一样

亚特兰蒂斯

解释

求面积模板,代码注释。此模板是按x扫描

模板

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;

double raw[N];// raw对应离散化之前的数
int cnt = 0;
map<double,int> val;// 对应离散化之后的数。
struct Point{
	double x,y1,y2;
	int k;
	bool operator <(const Point&T)const{
		return x < T.x;
	}
}p[N];

#define l(x) t[x].l
#define r(x) t[x].r
#define cnt(x) t[x].cnt
#define len(x) t[x].len

struct SegmentTree{
	int l,r,cnt;
	double len;
}t[N << 2];

void push_up(int p,int l,int r){
	if(cnt(p)) len(p) = raw[r+1] - raw[l];
	else if(l == r) len(p) = 0;
	else len(p) = len(p<<1) + len(p<<1|1);
}

void build(int p,int l,int r){
	l(p) = l,r(p) = r;cnt(p) = len(p) = 0;
	if(l == r) return ;
	int mid = (l + r) >> 1;
	build(p<<1,l,mid);build(p<<1|1,mid+1,r);
}

void update(int p,int l,int r,int k){
	if(l <= l(p) && r(p) <= r){
		cnt(p) += k;
		push_up(p,l(p),r(p));
		return ;
	}
	int mid = (l(p) + r(p)) >> 1;
	if(l <= mid) update(p<<1,l,r,k);
	if(r > mid) update(p<<1|1,l,r,k);
	push_up(p,l(p),r(p));
}

signed main(){
	IOS
    int n,kase = 1;
    while(cin>>n && n){
    	cnt = 0;
    	val.clear();
    	for(int i=0;i<n;i++){
    		double x1,x2,y1,y2; cin>>x1>>y1>>x2>>y2;
    		p[++ cnt] = {x1,y1,y2,1};
    		raw[cnt] = y1;
    		p[++ cnt] = {x2,y1,y2,-1};
    		raw[cnt] = y2;
		}
		sort(p+1,p+cnt+1);
		// 离散化
		sort(raw+1,raw+cnt+1);
		int len = unique(raw+1,raw+cnt+1) - raw - 1;
		for(int i=1;i<=len;i++) val[raw[i]] = i;
		
		build(1,1,len-1);
		double ans = 0;
		for(int i=1;i<cnt;i++){// 注意l~r区间维护的是raw[r+1] - raw[l] 的值
			int l = val[p[i].y1],r = val[p[i].y2]-1,k = p[i].k;
			update(1,l,r,k);
			ans += len(1) * (p[i+1].x - p[i].x);
		}
		cout<<"Test case #"<<kase++<<"\nTotal explored area: "<<fixed<<setprecision(2)<<ans<<"\n"<<endl;
	}
	return 0;
}

可持久化线段树(主席树)

参考博客:

OIWiki

【模板】可持久化线段树1(可持久化数组)

解释

无(看板子就行了)

模板

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;

int n,m;
int a[N],rt[N];

#define l(x) t[x].l
#define r(x) t[x].r
#define val(x) t[x].val

struct LSegmentTree{
	int l,r,val;
}t[N << 5];
int idx = 0;

int build(int l,int r){
	int root = ++idx;
	if(l == r){
		val(root) = a[l];
		return root;
	}
	int mid = (l + r) >> 1;
	l(root) = build(l,mid);
	r(root) = build(mid+1,r);
	return root;
}

int update(int p,int l,int r,int pos,int va){
	int root = ++idx;
	l(root) = l(p),r(root) = r(p);
	int mid = (l + r) >> 1;
	if(l == r){
		val(root) = va;
		return root;
	}
	if(pos <= mid) l(root) = update(l(root),l,mid,pos,va);
	else r(root) = update(r(root),mid+1,r,pos,va);
	return root;
}

int query(int p,int l,int r,int pos){
	int mid = (l + r) >> 1;
	if(l == r) return val(p);
	if(pos <= mid) return query(l(p),l,mid,pos);
	else return query(r(p),mid+1,r,pos);
}

signed main(){
	IOS
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    rt[0] = build(1,n);
    for(int i=1;i<=m;i++){
    	int nrt,op; cin>>nrt>>op;
    	if(op == 1){
    		int pos,val; cin>>pos>>val;
    		rt[i] = update(rt[nrt],1,n,pos,val);
		}else{
			int pos; cin>>pos;
			cout<<query(rt[nrt],1,n,pos)<<endl;
			rt[i] = rt[nrt];
		}
	}
    
	return 0;
}

【模板】可持久化线段树2(主席树)

解释

权值线段树加历史维护。 f ( i ) f(i) f(i) 表示第 i i i个版本,权值线段树维护的值。

利用前缀思想,求 [ l , r ] [l,r] [l,r]的第k小值等价于:权值线段树维护的信息 [ 1 , r ] − [ 1 , l − 1 ] [1,r] - [1,l-1] [1,r][1,l1] ,即 f ( r ) − f ( l − 1 ) f(r) - f(l-1) f(r)f(l1)

参考博客:OIWiki

模板

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;

int n,m,len;
int a[N],rt[N],b[N];

#define l(x) t[x].l
#define r(x) t[x].r
#define sum(x) t[x].sum

struct LSegmentTree{
	int l,r,sum;
}t[N << 5];
int idx = 0;

int find(int x){
	return lower_bound(b+1,b+len+1,x)-b;
}

int build(int l,int r){
	int root = ++idx;
	if(l == r) return root;
	int mid = (l + r) >> 1;
	l(root) = build(l,mid);
	r(root) = build(mid+1,r);
	return root;
}

int update(int p,int l,int r,int k){
	int root = ++idx;
	l(root) = l(p),r(root) = r(p),sum(root) = sum(p) + 1;
	int mid = (l + r) >> 1;
	if(l == r) return root;
	if(k <= mid) l(root) = update(l(root),l,mid,k);
	else r(root) = update(r(root),mid+1,r,k);
	return root;
}

int query(int lp,int rp,int l,int r,int k){
	int mid = (l + r) >> 1,suml = sum(l(rp)) - sum(l(lp));
	if(l == r) return l;
	if(k <= suml) return query(l(lp),l(rp),l,mid,k);
	else return query(r(lp),r(rp),mid+1,r,k-suml);
}

signed main(){
	IOS
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i],b[i] = a[i];
    
    sort(b+1,b+n+1);
    len = unique(b+1,b+n+1)-b-1;
    for(int i=1;i<=n;i++) a[i] = find(a[i]);
    
    rt[0] = build(1,len);
    for(int i=1;i<=n;i++) rt[i] = update(rt[i-1],1,len,a[i]);
    while(m --){
    	int l,r,k; cin>>l>>r>>k;
    	cout<<b[query(rt[l-1],rt[r],1,len,k)]<<endl;
	}
	return 0;
}

单点修改区间查询第k小值

解释

考虑静态的时候,如上,只需要求的版本前缀和,就可以快速求得第k小值。当涉及到单点修改时,可以把每一棵树看成一个点,用树状数组进行维护,就可以快速修改前缀和。查询稍做修改,对比静态,每次查询都是用的当前区间的所有树根,具体看代码 参考博客

模板

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;

#define ls(x) t[x].ls
#define rs(x) t[x].rs
#define cnt(x) t[x].cnt

struct LSegmentTree{
	int ls,rs,cnt;
}t[N * 500];
int idx = 0;
int rt[N],a[N],len;
int n,m;
vector<int> ve;

struct Query{
	char op;
	int l,r,k;
}q[N];

int find(int x){
	return lower_bound(ve.begin(),ve.end(),x)-ve.begin() + 1;
}

// 主席树
int update(int p,int l,int r,int pos,int val){
	int root = ++idx;
	ls(root) = ls(p),rs(root) = rs(p),cnt(root) = cnt(p) + val;
	if(l == r) return root;
	int mid = (l + r) >> 1;
	if(pos <= mid) ls(root) = update(ls(root),l,mid,pos,val);
	else rs(root) = update(rs(root),mid+1,r,pos,val);
	return root;
}

int rt1[N],rt2[N],cnt1,cnt2;
int query(int l,int r,int k){
	if(l == r) return ve[l-1]; // 下标从0开始
	int mid = (l + r) >> 1;
	int sum = 0;
	for(int i=0;i<cnt1;i++) sum -= cnt(ls(rt1[i])); // 类比静态
	for(int i=0;i<cnt2;i++) sum += cnt(ls(rt2[i]));
	if(k <= sum){
		for(int i=0;i<cnt1;i++) rt1[i] = ls(rt1[i]);
		for(int i=0;i<cnt2;i++) rt2[i] = ls(rt2[i]);
		return query(l,mid,k);
	}else{
		for(int i=0;i<cnt1;i++) rt1[i] = rs(rt1[i]);
		for(int i=0;i<cnt2;i++) rt2[i] = rs(rt2[i]);
		return query(mid+1,r,k-sum);
	}
}

// 树状数组
#define lowbit(x) ((x) & (-x))
void change(int x,int p,int val){
	for(int i=x;i<=n;i+=lowbit(i)) rt[i] = update(rt[i],1,len,p,val);
} 

int ask(int l,int r,int k){
	cnt1 = cnt2 = 0;
	for(int i=l;i;i-=lowbit(i)) rt1[cnt1++] = rt[i];
	for(int i=r;i;i-=lowbit(i)) rt2[cnt2++] = rt[i];
	return query(1,len,k);
}

signed main(){
	IOS
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i],ve.push_back(a[i]);
    for(int i=1;i<=m;i++){
    	cin>>q[i].op;
    	if(q[i].op == 'C'){
    		int x,y; cin>>x>>y;
    		q[i].l = x,q[i].r = y;
    		ve.push_back(y);
		}else{
			int l,r,k; cin>>l>>r>>k;
			q[i].l = l,q[i].r = r,q[i].k = k;
		}
	}
	// 离散化 
	sort(ve.begin(),ve.end());
	ve.erase(unique(ve.begin(),ve.end()),ve.end());
	len = ve.size();
	for(int i=1;i<=n;i++) a[i] = find(a[i]);
	for(int i=1;i<=m;i++) if(q[i].op == 'C') q[i].r = find(q[i].r);
	for(int i=1;i<=n;i++) change(i,a[i],1);
	
    for(int i=1;i<=m;i++){
    	char op = q[i].op;
    	if(op == 'Q'){
    		int l = q[i].l,r = q[i].r,k = q[i].k;
    		cout<<ask(l-1,r,k)<<endl;
		}else{
			int x = q[i].l,y = q[i].r;
			change(x,a[x],-1);
			a[x] = y;// 记住在原数组修改
			change(x,a[x],1);
		}
	}
	return 0;
}

更多例题

P3567 用权值线段树维护区间和,持久化维护前缀。代码

P1383 可持久化数组维护,记录每一个版本插入的长度,undo操作相当于把距离当前x个版本的根节点给它 代码

P3939 可以尝试,但是好像会被卡,然后学疯了。代码

StoneGames(ICPC昆明) 代码

可持久化字典树

参考博客:

OIWiki 例题所给的代码被数据加强给卡掉了,但是不影响理解。

最大异或和

解释

设s(x) 表示1 ~ x 的异或和。根据异或性质,题目所求max( a( p ) ^ … ^ a( n ) ^ x) -> s( p - 1 ) ^ s ( n ) ^ x​

而 s ( n ) ^ x 是一个定值,所以只需要知道s ( p - 1) 即 s ( x ) ( x > = l − 1 ) a n d ( x < = r − 1 ) (x >= l-1) and (x <= r-1) (x>=l1)and(x<=r1) .注意,

s ( 0 ) 为 0 . 用主席树的思想,维护一个前缀,每一个s ( x ) 每一位0,1出现的次数,在查询的时候,如果接下来访问的那一位有想要的0或者1,则累加答案。即实现可持久化权值线段树,每个节点代表位,节点存两个信息,1出现的次数和0出现的次数。

但是这题是用字典树来完成。想法差不多,只是维护字典树用val数组维护(代表当前位数当前值(0,1)出现的次数的前缀和),不用开权值线段树。

模板

#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
#include<bits/stdc++.h>
using namespace std;
const int N = 6e5 + 10;

int n,m;
int cnt = 0;
int rt[N],s[N],son[N*29][2],val[N*29];
void insert(int lp,int rp,int v){ // 插入
	for(int i=28;i>=0;i--){
		val[rp] = val[lp] + 1;// 维护两个版本之间相同“含义(代表值)”所出现的次数,用于维护前缀和。
		int t = (v & (1 << i)) ? 1 : 0;
		son[rp][!t] = son[lp][!t];
		son[rp][t] = ++cnt; 
		lp = son[lp][t];
		rp = son[rp][t];
	}
	val[rp] = val[lp] + 1;
}
int query(int lp,int rp,int v){
	int res = 0;
	for(int i=28;i>=0;i--){
		int t = (v & (1 << i)) ? 1 : 0;
		if(val[son[rp][!t]] - val[son[lp][!t]] > 0){// 当前区间这个位置代表值有元素
			res += (1 << i);
			rp = son[rp][!t];
			lp = son[lp][!t];
		}else{
			rp = son[rp][t];
			lp = son[lp][t];
		}
	}
	return res; 
}

signed main(){
	IOS
    cin>>n>>m;
    rt[0] = ++cnt,insert(0,rt[0],0); // 预先把0处理进来,当l为1时,s0=0。
	for(int i=1;i<=n;i++){
		cin>>s[i],s[i] ^= s[i-1];
		rt[i] = ++cnt,insert(rt[i-1],rt[i],s[i]);
	}
	
	while(m --){
		string op; cin>>op;
		if(op == "A"){
			cin>>s[++n];
			s[n] ^= s[n-1];
			rt[n] = ++cnt;
			insert(rt[n-1],rt[n],s[n]);
		}else{
			int l,r,k; cin>>l>>r>>k;
			l -- ,r -- ;
			if(l == 0) cout<<query(0,rt[r],s[n]^k)<<endl;
			else cout<<query(rt[l-1],rt[r],s[n]^k)<<endl;
		}
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值