「学习笔记」线段树

目录

    • 1.建立+单点更新+查询
      • (1)建树模板
      • (2)单点更新模板
      • (3)查询模板
      • (4)例题
    • 2.区间更新
      • (1)区间更新模板
      • (2)例题
    • 3.维护可和信息
      • (1)常见的可和信息
      • (2)例题

1.建立+单点更新+查询

(1)建树模板

#define ls p<<1
#define rs p<<1|1
#define lson l,m,ls
#define rson m+1,r,rs
int sum[maxn<<2];//空间要开四倍
void push_up(int p){
    sum[p]=sum[ls]+sum[rs];
}
void build(int l,int r,int p){
    if(l==r){
    	scanf("%d",&sum[p]);
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    push_up(p);//向上更新
}
//调用 build(1,n,1);

(2)单点更新模板

void update(int x,int num,int l,int r,int p){
    if(l==r){
        sum[p]+=num;
        return;
    }
    int m=(l+r)>>1;
    if(x<=m)update(x,num,lson);
    else update(x,num,rson);
    push_up(p);
}
//将第五个元素的值加num
//调用 update(5,num,1,n,1);

(3)查询模板

int query(int L,int R,int l,int r,int p){
    if(L<=l&&r<=R){
        return sum[p];
    }
    int m=(l+r)>>1;
    int ans=0;
    if(L<=m)ans+=query(L,R,lson);
    if(R>m)ans+=query(L,R,rson);
    return ans;
}
//查询1-3的值的和
//调用 query(1,3,1,n,1);

(4)例题

求最小逆序对
题目链接点这里

题意:不断把第一个数放在末尾,求在此过程中序列的最小逆序对数
解析:由于数值间唯一且不重复,且n不大,所以可以先以原序列建一棵线段树,维护不同时段1-n数的个数,在进行一次统计之后,进行一次更新,因为求逆序对数也即是统计当前时刻在线段树中比其大的数的个数。
然后就是不断把第一个数放到末尾,设当前数为x,由于在未放之前,比其大的数有n-x个,比其小的有x-1个,所以放到末尾之后,逆序对数相对于x未放之前增加了n-x-(x-1)个,所以扫一遍取最小就行了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int inf=0x3f3f3f3f;
const int maxn=5e3+10;
#define lson l,m,p<<1
#define rson m+1,r,p<<1|1
int n;
int arr[maxn];
int sum[maxn<<2];
void push_up(int p){
    sum[p]=sum[p<<1]+sum[p<<1|1];
}
void build(int l,int r,int p){
    sum[p]=0;
    if(l==r){
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
}
int query(int L,int R,int l,int r,int p){
    if(L<=l&&r<=R){
        return sum[p];
    }
    int m=(l+r)>>1;
    if(R<=m)return query(L,R,lson);
    if(L>m)return query(L,R,rson);
    return query(L,m,lson)+query(m+1,R,rson);
}
void update(int num,int l,int r,int p){
    if(l==r){
        sum[p]++;
        return;
    }
    int m=(l+r)>>1;
    if(num<=m)update(num,lson);
    else update(num,rson);
    push_up(p);
}
int main() {
	while(~scanf("%d",&n)){
        int sum=0;
        build(1,n,1);
        for(int i=1;i<=n;i++){
            scanf("%d",&arr[i]);
            ++arr[i];//将0-n-1变成1-n
            sum+=query(arr[i],n,1,n,1);
            update(arr[i],1,n,1);
        }
        int ans=sum;
        for(int i=1;i<=n;i++){
            sum+=n-arr[i]-(arr[i]-1);
            ans=min(ans,sum);
        }
        cout<<ans<<'\n';
	}
	return 0;
}

2.区间更新

(1)区间更新模板

#define ls p<<1
#define rs p<<1|1
int sum[maxn<<2],lazy[maxn<<2];
void push_down(int p,int l,int r){//区间每个数都加上一个数
    if(lazy[p]==0)return;
    int m=(l+r)>>1;
    lazy[ls]+=lazy[p];
    lazy[rs]+=lazy[p];
    sum[ls]+=lazy[p]*(m-l+1);
    sum[rs]+=lazy[p]*(r-m);
    lazy[p]=0;
}
//相应的query和update也有部分修改

(2)例题

题目链接点这里
区间更新的一道裸体,就不过多解释了

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <queue>
#include <stack>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int inf=0x3f3f3f3f;
const int maxn=1e5+10;
#define ls p<<1
#define rs p<<1|1
#define lson l,m,p<<1
#define rson m+1,r,p<<1|1
int n,a,b;
int sum[maxn<<2],lazy[maxn<<2];
void push_up(int p){
    sum[p]=sum[ls]+sum[rs];
}
void push_down(int p,int l,int r){
    if(lazy[p]==0)return;
    int m=(l+r)>>1;
    lazy[ls]+=lazy[p];
    lazy[rs]+=lazy[p];
    sum[ls]+=lazy[p]*(m-l+1);
    sum[rs]+=lazy[p]*(r-m);
    lazy[p]=0;
}
void build(int l,int r,int p){
    sum[p]=0;
    lazy[p]=0;
    if(l==r){
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
}
int query(int L,int R,int l,int r,int p){
    if(L<=l&&r<=R){
        return sum[p];
    }
    push_down(p,l,r);
    int m=(l+r)>>1;
    if(R<=m)return query(L,R,lson);
    if(L>m)return query(L,R,rson);
    return query(L,m,lson)+query(m+1,R,rson);
}
void update(int L,int R,int l,int r,int p){
    if(L<=l&&r<=R){
        lazy[p]++;
        sum[p]+=(r-l+1);
        return;
    }
    push_down(p,l,r);//在继续修改前,先检查是否要下传标记
    int m=(l+r)>>1;
    if(R<=m)update(L,R,lson);
    else if(L>m)update(L,R,rson);
    else update(L,m,lson),update(m+1,R,rson);
    push_up(p);//回溯更新每个节点的sum,因为每个节点的值改变了
}
int main() {
	while(~scanf("%d",&n)&&n){
        build(1,n,1);
        for(int i=1;i<=n;i++){
            scanf("%d%d",&a,&b);
            update(a,b,1,n,1);
        }
        for(int i=1;i<=n;i++){
            printf("%d%c",query(i,i,1,n,1),i==n?'\n':' ');
        }
	}
	return 0;
}

链接点这里

题意:给你一个序列s[],s[i]表示它之前所有小于它的数的和,让你求p[]数组,元素大小在[1,n]范围且唯一。
解析:我们不难发现1在s[]中最后一个0所在位置处,去掉1并将其后所有元素减一后,2就在新序列最后一个0所在位置,以此类推,就能求出p数组。由于涉及区间更新,和查询,所以用线段树可以高效解决。
复杂度:O(nlogn)

#include <iostream>
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
const ll inf=1e15;
#define faster ios::sync_with_stdio(0),cin.tie(0)
#define lson l,m,p<<1
#define rson m+1,r,p<<1|1
#define ls p<<1
#define rs p<<1|1

int n;
ll s[maxn];
ll mm[maxn<<2],lazy[maxn<<2];
int ans[maxn];
void push_up(int p){
    mm[p]=min(mm[ls],mm[rs]);
}
void push_down(int p){
    if(lazy[p]==0)return;
    lazy[ls]+=lazy[p];
    lazy[rs]+=lazy[p];
    mm[ls]+=lazy[p];//如果维护的是和则需要乘(m-l+1)
    mm[rs]+=lazy[p];//上同需要乘(r-m)
    lazy[p]=0;
}
void build(int l,int r,int p){
    if(l==r){
        mm[p]=s[l];
        return ;
    }
    int m=(l+r)>>1;
    build(lson);
    build(rson);
    push_up(p);
}
void update(int pos,ll x,int l,int r,int p){
    if(l==r){
        mm[p]=x;lazy[p]=0;
        return ;
    }
    push_down(p);
    int m=(l+r)>>1;
    if(pos<=m)update(pos,x,lson);
    else update(pos,x,rson);
    push_up(p);
}
void update(int L,int R,ll x,int l,int r,int p){
    if(L<=l&&r<=R){
        lazy[p]+=x;
        mm[p]+=x;
        return ;
    }
    push_down(p);
    int m=(l+r)>>1;
    if(R<=m)update(L,R,x,lson);
    else if(L>m)update(L,R,x,rson);
    else update(L,m,x,lson),update(m+1,R,x,rson);
    push_up(p);
}
int query(int l,int r,int p){//查询最后一个0所在位置
    if(l==r){
        return l;
    }
    push_down(p);
    int m=(l+r)>>1;
    if(mm[rs]==0)return query(rson);
    return query(lson);
}
int main()
{
    faster;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>s[i];
    }
    build(1,n,1);
    for(int i=1;i<=n;i++){
        int pos=query(1,n,1);
        ans[pos]=i;
        update(pos,inf,1,n,1);
        if(pos<n)update(pos+1,n,(ll)-i,1,n,1);
    }
    for(int i=1;i<=n;i++)printf("%d%c",ans[i],i==n?'\n':' ');
    return 0;
}

3.维护可和信息

(1)常见的可和信息

(1)最大值,最小值
(2)数字之和
(3)gcd

(2)例题

题目链接点这里

题意:简而言之就是一个序列初始都为1,现在每次可以使一个点变为0,每次也可以把之前变为0的点变回1,现在要求你在线查询其中的某一点(包括他自己)的1的最大连续长度。
解析:用线段树维护,对于每个点,我们需要维护三个信息:
1.从该区间左端点出发连续的最大长度;(设其为lx)
2.从该区间右端点出发连续的最大长度;(设其为rx)
3.该区间的最大连续长度; (设其为mx)
单点更新时,就是把对应的叶子节点变为1或0,然后pushup,pushup时要注意怎样合并这三个信息。
一.首先父节点的lx最小也应该是左儿子的lx,rx同理;
然后其mx一定是:左儿子的mx,右儿子的mx,左儿子的rx+右儿子的lx 三者的最大值。(应该不难理解)
现在还有一个问题就是如果他左儿子的lx是满的(即lx值为左儿子维护区间大小),那么它的lx就需要加上右儿子的lx;他的rx同理。(此处重点理解一下,最好在本子上画画,就很清晰了)
二.然后就是查询了,我们要查包含某个点的最大连续1的长度,也就是查询包括这个点的所有区间中1最多的那个。
所以从根节点出发查询即可,注意剪枝。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <stack>
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
const int inf=0x3f3f3f3f;
const int maxn=5e4+10;
#define ls p<<1
#define rs p<<1|1
#define lson l,m,ls
#define rson m+1,r,rs
#define faster ios::sync_with_stdio(0),cin.tie(0)
int m,n;
struct tree{
    int l,r,lx,rx,mx;
}sum[maxn<<2];
void pushup(int p){
    sum[p].lx=sum[ls].lx;
    sum[p].rx=sum[rs].rx;
    sum[p].mx=max(sum[ls].mx,sum[rs].mx);
    sum[p].mx=max(sum[p].mx,sum[ls].rx+sum[rs].lx);
    if(sum[ls].lx==sum[ls].r-sum[ls].l+1){
        sum[p].lx+=sum[rs].lx;
    }
    if(sum[rs].rx==sum[rs].r-sum[rs].l+1){
        sum[p].rx+=sum[ls].rx;
    }
}
void build(int l,int r,int p){
    sum[p].l=l;sum[p].r=r;sum[p].lx=sum[p].rx=sum[p].mx=r-l+1;
    if(l==r)return;
    int m=(l+r)>>1;
    build(lson);
    build(rson);
}
void update(int x,int num,int p){
    if(sum[p].l==sum[p].r){
        sum[p].lx=sum[p].rx=sum[p].mx=num;
        return ;
    }
    int m=(sum[p].l+sum[p].r)>>1;
    if(x<=m)update(x,num,ls);
    if(x>m)update(x,num,rs);
    pushup(p);
}
int query(int x,int p){
	//如果区间是满的或是0就可以返回了
    if(sum[p].l==sum[p].r||sum[p].mx==0||sum[p].mx==sum[p].r-sum[p].l+1){
        return sum[p].mx;
    }
    int m=(sum[p].l+sum[p].r)>>1;
    if(x<=m){
    	//要求的区间会不会涉及到右儿子
        if(x>=sum[ls].r-sum[ls].rx+1){
            return query(x,ls)+query(m+1,rs);
        }
        else return query(x,ls);
    }
    else {
        if(x<=sum[rs].l+sum[rs].lx-1){
            return query(x,rs)+query(m,ls);
        }
        else return query(x,rs);
    }
}
stack<int> s;
int main()
{
    while(~scanf("%d%d",&n,&m)){
        while(!s.empty())s.pop();
        build(1,n,1);
        for(int i=1;i<=m;i++){
            char t;int x;
            cin>>t;
            if(t=='D'){
                scanf("%d",&x);
                update(x,0,1);
                s.push(x);
            }
            if(t=='Q'){
                scanf("%d",&x);
                cout<<query(x,1)<<'\n';
            }
            if(t=='R'){
                if(!s.empty()){
                    update(s.top(),1,1);
                    s.pop();
                }
            }
        }
    }
    return 0;
}

你可能感兴趣的:(模板,数据结构,数据结构,线段树)