st表、树状数组、线段树和主席树

树状数组 :

单点修改 (+ - * /)
区间修改( + - 最值)其中+ -要利用查分数组来实现
单点查询 传统方法
区间查询 最值得话就循规蹈矩的进行查找
但是如果使用区间修改过(查分数组)后再进行区间查询,则要进行计算一下

∑ni = 1A[i] = ∑ni = 1 ∑ij = 1D[j];

	则A[1]+A[2]+...+A[n]

	= (D[1]) + (D[1]+D[2]) + ... + (D[1]+D[2]+...+D[n]) 
 
	= n*D[1] + (n-1)*D[2] +... +D[n]
所以说,树状数组对于去区间的修改很难办到,可以用数组维护差分数组,但是这样的话对区间的查询就很难办到了,因此,树状数组适合维护单点修改,单点查询和区间查询,对于区间的修改我们使用树状数组进行维护即可。

单点修改

void update(int pos,int val )
{
	for(int i=pos;i<=maxn;i+=lowbit(i))
	{
		t[i]+=val;//如果是区间最大值则要修改一下 
		
	}		
} 

区间查询

void query(int l,int r)
{
	int sum=0;
	
	while(l<=r)
	{
		for(;r-lowbit(r)>=l;r-=lowbit(r))
		{
			sum+=tree[r];
		}
		sum+=tree[r];
		r--;		
	}	 	
} 

区间修改 单点查询 d数组是查分数组 第I项的值则是前i项的d[i]和

void  update(int l,int r,int val)
{	
	d[l]+=val;
	d[r+1]-=val;	
} 

ST表求区间最大值

在这里插入图片描述在这里插入图片描述

 for(int i=1;i<=n;i++)
 st[i][0]=a[i];
 for(int j=0;(1<<j)<=n;j++)
 {
 	for(int i=1;i+(1<<j)-1<=n;i++)
 	{
 		st[i][j]=max(st[i][j-1],st[i+(1<<(j-1))][j-1]);
 		
 	}
 } 
 void query(int l,int r)
 {
 	int k=log2(r-l+1);
 	return max(f[l][k],f[r-(1<<k)+1][k]);
 	
 } 

线段树

线段树就是用来维护一个区间而建立的一棵树,线段树中每一个节点维护的都是一个区间
如果父节点维护的区间是[ l , r ] ,那么两个子节点s维护的区间分别为[ l,( l+r )/2 ] 和 [ ( l+r )/2+1, r ]。
在这里插入图片描述在这里插入图片描述

push_down

在这里插入图片描述
这里给一个线段树维护一段区间内的和的代码

const int maxn=1e5+10;
long long a[maxn],lazy[maxn<<2];
long long tree[maxn<<2];
void push_up(int rt)
{
	tree[rt]=tree[rt<<1]+tree[rt<<1|1];
}
void push_down(int rt,int l,int r)
{
	if(lazy[rt])
	{
		lazy[rt<<1]+=lazy[rt];
		lazy[rt<<1|1]+=lazy[rt];
		
		int m=(l+r)>>1;
		tree[rt<<1]+=(m-l+1)*lazy[rt];
		tree[rt<<1|1]+=(r-m)*lazy[rt];
		lazy[rt]=0;
	}		
}
void build(int rt,int l,int r)
{
	if(l==r)
	{
		tree[rt]=a[l];
		return ;
	}
	
	int m=(l+r)>>1;
	build(rt<<1,l,m);
	build(rt<<1|1,m+1,r);
	push_up(rt);
	
}

void update(int rt,int l,int r,int ll,int rr,int val)
{
//	if(l==r)
//	{
//		
//		tree[rt]+=val;
//		return ;
//	}
	if(l>=ll&&rr>=r)
	{
		lazy[rt]+=val;
		tree[rt]+=(r-l+1)*val;
		return;
	}
	
	push_down(rt,l,r);
	
	int m=(r+l)>>1;
	if(m>=ll) update(rt<<1,l,m,ll,rr,val);
	if(m+1<=rr) update(rt<<1|1,m+1,r,ll,rr,val);
	
	push_up(rt);
}

long long query(int rt,int l,int r,int ll,int rr)
{
	long long res=0;
	
//	if(l==r)
//	{
//		return tree[rt];		
//	}
	if(l>=ll&&r<=rr)
	{
		return tree[rt];
	}
	
	push_down(rt,l,r);
	int m=(l+r)>>1;
	if(m>=ll) res+=query(rt<<1,l,m,ll,rr);
	if(m+1<=rr) res+=query(rt<<1|1,m+1,r,ll,rr);
	
	return res;
}

主席树

主席树就是可持久化线段树,线段树经过若干次修改后,仍然能找到原来某次修改前的线段树的信息的一种数据结构
主席树空间一般开40倍左右

代码中我注释的部分是建树是先整个建成一棵树,等到以后有更新操作了在重新建另一棵树

const int maxn=1e5;
int root[maxn];//这里是用来保存第i次更新后根节点的下标的 
struct node{
	int l,r; //这里l和r记录的是此节点的左孩子和右孩子下标,不是它维护的区间范围 
	int val; //维护的值 
	int lazy;
	node()
	{
		val=0;
		lazy=0;
	}
}tree[maxn*40];
 
void init()
{
	fill(root,root+maxn,0);
}

void push(int rt)
{
	tree[rt].val=tree[tree[rt].l].val+tree[tree[rt].r].val;	
}

void push_down(int rt,int l,int r)
{
	if(tree[rt].lazy)
	{
		tree[tree[rt].l].lazy+=tree[rt].lazy;
		tree[tree[rt].r].lazy+=tree[rt].lazy;
		
		int m=(l+r)>>1;
		tree[tree[rt].l].val+=(m-l+1)*tree[rt].lazy;
		tree[tree[rt].r].val+=(r-m)*tree[rt].lazy;
		tree[rt].lazy=0;	
	}	
}

//这里的x和y分别表示这一个根节点个它前一个根节点的下标 
/*void build(int &x,int y,int l,int r,int p)
{
	x=++cnt;
	tree[cnt]=tree[y];
	
	
	if(l==r)
	{
		tree[cnt].val=a[l];
		return ;
	}
	
	int m=(l+r)>>1;
	if(p<=m) build(tree[cnt].l,tree[y].l,l,m,p);
	if(p>=m+1)	 build(tree[cnt].r,tree[y].r,m+1,r,p);	
	
	push_up(cnt);
}*/

void build(int &x,int l,int r)
{
	x=++cnt;
	if(l==r)
	{
		tree[x].val=a[l];
		return ;
	}
	
	int m=(r+l)>>1;
	build(tree[x].l,l,m);
	build(tree[x].r,m+1,r);
	
	push_up(x);	
}

void update(int &x,int y,int l,int r,int ll,int rr,int v)
{
	x=++cnt;
	tree[x]=tree[y];
	
	if(l<=ll&&rr>=r)
	{
		tree[x].lazy+=v;
		tree[x].val+=(r-l+1)*v;
		return;
	}
	
	push_down(rt,l,r);
	
	int m=(l+r)>>1;
	if(m>=ll) update(tree[x].l,tree[y].r,l,m,ll,rr,v);
	if(m+1<=rr) update(tree[x].r,tree[y].r,m+1,r,ll,rr,v);
	
	push_up(rt);	
}

int query(int x,int l,int r,int ll,int rr)
{
	if(l>=ll&&rr>=r)
	{
		return tree[x].val;		
	}
	
	push_down(x,l,r);
	
	int res=0;
	int m=(r+l)>>1;
	if(ll<=m) res+=query(tree[x].l,l,m,ll,rr);
	if(rr>=m+1) res+=query(tree[x].r,m+1,r,ll,rr);
	
	return res;	
}

这个代码是在建树的时候每加入一个节点新建一棵树,这样方便对一个区间内的节点进行操作

const int maxn=1e5;
int root[maxn];//这里是用来保存第i次更新后根节点的下标的 
struct node{
	int l,r; //这里l和r记录的是此节点的左孩子和右孩子下标,不是它维护的区间范围 
	int val; //维护的值 
	int lazy;
	node()
	{
		val=0;
		lazy=0;
	}
}tree[maxn*40];
 
void init()
{
	fill(root,root+maxn,0);
}

void push(int rt)
{
	tree[rt].val=tree[tree[rt].l].val+tree[tree[rt].r].val;	
}

void push_down(int rt,int l,int r)
{
	if(tree[rt].lazy)
	{
		tree[tree[rt].l].lazy+=tree[rt].lazy;
		tree[tree[rt].r].lazy+=tree[rt].lazy;
		
		int m=(l+r)>>1;
		tree[tree[rt].l].val+=(m-l+1)*tree[rt].lazy;
		tree[tree[rt].r].val+=(r-m)*tree[rt].lazy;
		tree[rt].lazy=0;	
	}	
}

//这里的x和y分别表示这一个根节点个它前一个根节点的下标 
void build(int &x,int y,int l,int r,int p)
{
	x=++cnt;
	tree[cnt]=tree[y];
	
	
	if(l==r)
	{
		tree[cnt].val=a[l];
		return ;
	}
	
	int m=(l+r)>>1;
	if(p<=m) build(tree[cnt].l,tree[y].l,l,m,p);
	if(p>=m+1)	 build(tree[cnt].r,tree[y].r,m+1,r,p);	
	
	push_up(cnt);
}

void update(int &x,int y,int l,int r,int ll,int rr,int v)
{
	x=++cnt;
	tree[x]=tree[y];
	
	if(l<=ll&&rr>=r)
	{
		tree[x].lazy+=v;
		tree[x].val+=(r-l+1)*v;
		return;
	}
	
	push_down(rt,l,r);
	
	int m=(l+r)>>1;
	if(m>=ll) update(tree[x].l,tree[y].r,l,m,ll,rr,v);
	if(m+1<=rr) update(tree[x].r,tree[y].r,m+1,r,ll,rr,v);
	
	push_up(rt);	
}

int query(int x,int l,int r,int ll,int rr)
{
	if(l>=ll&&rr>=r)
	{
		return tree[x].val;		
	}
	
	push_down(x,l,r);
	
	int res=0;
	int m=(r+l)>>1;
	if(ll<=m) res+=query(tree[x].l,l,m,ll,rr);
	if(rr>=m+1) res+=query(tree[x].r,m+1,r,ll,rr);
	
	return res;	
}

参考例题

1.求第k小

#include<cstdio>
#include<vector>
#include<algorithm>
#include<iostream>
using namespace std;
const int maxn = 1e5+5;
int cnt,root[maxn],a[maxn];
//root[i] 第i课线段树根节点的位置 
//cnt 用作开辟新的树节点。
struct node{
	int l,r;//左右儿子结点编号,因为不满足2*rt规律 
	int sum;//代表(l,r)区间上和是多少 
}T[maxn*40];
vector<int> v;
int getid(int x){
	return lower_bound(v.begin(),v.end(),x)-v.begin()+1;
}
/** update函数介绍
l,r 代表线段树递归的区间,x代表前一棵树的节点位置,y是后面的节点位置。
在递归的过程中,将需要修改的树节点复制到新开辟节点,改变自己的sum,
也就是自加1,顺便改变上一个的孩子节点
所以传参是引用传参;//线段树区间统计,sum代表在这个区间数的个数。
*/
//update(1,n,root[i],root[i-1],getid(a[i]));
void update(int l,int r,int &x,int y,int p){
	T[++cnt] = T[y];//左右son和sum都先连接 
	T[cnt].sum++;
	x = cnt;
	if(l==r) return ;
	int m = (l+r)>>1;
	if(m>=p) update(l,m,T[x].l,T[y].l,p);
	else update(m+1,r,T[x].r,T[y].r,p);
}
/**  query函数介绍
因为是查找第K小,所以在查找时候只需要看左边孩子节点,
两棵线段树sum做差,便得到这个区间的值
比如 root[R]-root[L-1] ,则代表区间 [L,R] 的数的统计
所以 S=(R线段树左孩子的sum)-(L-1线段树左孩子的sum)
 如果 S>=K(第K小),所以第K小肯定在左儿子节点,
否则,右节点,并且在右边区间再找剩下的 K-S,即可。
*/
//query(1,n,root[l],root[r],k); 
int query(int l,int r,int x,int y,int k){
	if(l == r) return l;
	int m = (l+r)>>1;
	int sum = T[T[y].l].sum - T[T[x].l].sum;
	if(k<=sum) return query(l,m,T[x].l,T[y].l,k);
	else return query(m+1,r,T[x].r,T[y].r,k-sum);
}
 
int main(){
	int n,m,x,y,k;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++) {
		scanf("%d",&a[i]);
		v.push_back(a[i]);
	}
	sort(v.begin(),v.end());
	v.erase(unique(v.begin(),v.end()),v.end());//去重
 
	for(int i=1;i<=n;i++) 
		update(1,n,root[i],root[i-1],getid(a[i]));
	for(int i=1;i<=m;i++) {	
		scanf("%d%d%d",&x,&y,&k);	
		printf("%d\n",v[query(1,n,root[x-1],root[y],k)-1]);//why -1
	}	
	return 0;
}

2.题意:

一个长度为n的数组,4种操作 : (1)C l r d:区间[l,r]中的数都加1,同时当前的时间戳加1 。 (2)Q l r:查询当前时间戳区间[l,r]中所有数的和 。 (3)H l r t:查询时间戳t区间[l,r]的和 。 (4)B t:将当前时间戳置为t 。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
#define pb push_back 
typedef long long ll;
const int maxn = 100009;

struct node {
    int l,r;
    ll sum,lazy;
}T[maxn*40];
int root[maxn],cur,tot;
ll a[maxn];


void build(int l,int r,int &pos)
{
    pos = tot++;
    T[pos].sum = T[pos].lazy = 0;
            // T[pos].l = l,T[pos].r = r;
    if(l==r)
    {
        T[pos].sum = a[l];
        return;
    }
    int mid = (l+r)>>1;
    build(l,mid,T[pos].l);
    build(mid+1,r,T[pos].r);
    T[pos].sum = T[T[pos].l].sum + T[T[pos].r].sum;
}
void update(int L,int R, int &x, int y , int l, int r, int d)//这里的x和上面的pos类似
{
    x = tot++;
    T[x] = T[y];
    
    if(l>=L && r<=R)
    {
        T[x].sum += 1ll*(r - l + 1) * d;                    //这里用目标区间的L,R;因为目标区间每个值都要加上
        T[x].lazy += d;
        return;
    }
    int mid = (l+r)>>1;
    if(L <= mid)
        update(L, R, T[x].l, T[y].l, l, mid, d);
    if(R > mid)                                   
        update(L, R, T[x].r, T[y].r, mid+1,r, d);

    T[x].sum = T[T[x].l].sum + T[T[x].r].sum + 1ll*(r-l+1) * T[x].lazy;
}
ll query(int L,int R,int x,int l,int r)
{
            if(L<=l && R>=r)
            {
                return T[x].sum;
            }
            ll ans = 1ll*T[x].lazy*(min(R,r)-max(L,l) + 1);
            int mid = (l+r)>>1;
            if(R  > mid)
                ans += query(L,R,T[x].r, mid+1, r);
            if(L<=mid)
                ans += query(L,R,T[x].l, l,mid);
            return ans;
}
int main()
{
            int n,m;
            int flag = 0;
            while(~scanf("%d%d", &n, &m))
            {   
                if(flag)puts("");
                flag = 1;
                tot = 0,cur = 0;
                for(int i=1; i<=n; i++)
                {
                    scanf("%lld", &a[i]);
                }
                // root[0] = tot++;
                build(1,n,root[0]); 
                while(m--)
                {
                    char op[20];
                    scanf("%s" , op);
                    if(op[0]=='Q')
                    {
                        int l,r;
                        scanf("%d%d",&l,&r);
                        printf("%lld\n",query(l,r,root[cur],1,n));
                    }
                    else if(op[0]=='C')
                    {
                        int l,r;
                        ll d;
                        scanf("%d%d%lld", &l, &r, &d);
                        cur++;
                        update(l,r,root[cur],root[cur-1], 1, n, d);
                    }
                    else if(op[0]=='H')
                    {   
                        int l,r,t;
                        scanf("%d%d%d",&l,&r,&t);
                        printf("%lld\n",query(l,r,root[t],1,n));
                    }
                    else if(op[0]=='B')
                    {
                        scanf("%d",&cur);
                    }
                }
            }
            return 0;
}
发布了31 篇原创文章 · 获赞 13 · 访问量 766

猜你喜欢

转载自blog.csdn.net/weixin_43310882/article/details/99091222
今日推荐