树套树思想


序言

  • 线段树是一种基于把查询区间划分为 l o g ( n ) log(n) log(n)个互相没有交集的小区间,并借助区间可加性维护的,所以只要把区间划分完成就可以独立查询修改
  • 注意变量划分,避免重名

一,线段树套 multiset

  • 注意是多重集
  • 注意是lower还是upper
  • 区间插入一个哨兵避免特判
  • 建树时区间所有的点位置都要进入SET
  • 线段树开4倍,多留余量

1 pos x,将 p o s pos pos 位置的数修改为 x x x
2 l r x,查询整数 x x x 在区间 [ l , r ] [l,r] [l,r] 内的前驱(前驱定义为小于 x x x,且最大的数)。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>

using namespace std;
const int N = 2e5+100;
const int INF = 1e9; 
struct node 
{
    int l,r;
    multiset<int>s;
}tr[N];
int w[N];

void build (int u,int l,int r)
{
    tr[u]={l,r};
    tr[u].s.insert(INF);
    tr[u].s.insert(-INF);
    for (int i = l; i <= r; i ++ ) tr[u].s.insert(w[i]);
    if(l==r) return ;
    int mid = (l+r)>>1;
    build (u<<1,l,mid);
    build (u<<1|1,mid+1,r);
}

void modify(int u,int pos,int val)
{
    auto j = tr[u].s.find(w[pos]);
    tr[u].s.erase(j);
    tr[u].s.insert(val);
    if(tr[u].l==tr[u].r && tr[u].l==pos) return;
    int mid = tr[u].l+tr[u].r>>1;
    if(pos<=mid)modify(u<<1,pos,val);
    else modify(u<<1|1,pos,val);
}


int query(int u,int a,int b,int x)
{
    if(tr[u].l >= a && tr[u].r <= b)
    {
        auto it = tr[u].s.lower_bound(x);
        it--;
        return *it;
    }
    int mid = tr[u].l+tr[u].r >> 1;
    int res  = -INF;
    if(a<=mid) res=max(res,query(u<<1,a,b,x));
    if(b>mid) res=max(res,query(u<<1|1,a,b,x));
    return res;
}


int main()
{
    int n,m;
    cin >> n >> m;
    for (int i = 1; i <= n; i ++ ) cin >> w[i];
    build (1,1,n);
    while (m -- )
    {
        int op,x,y,val;
        scanf("%d%d%d",&op,&x,&y);
        if(op==1) modify(1,x,y), w[x]=y;
        else scanf("%d",&val),printf("%d\n",query(1,x,y,val));
    }
    
    return 0;
}

二,线段树套平衡树

第一行两个数 n , m n,m n,m,表示长度为 n n n 的有序序列和 m m m 个操作。
第二行有 n n n 个数,表示有序序列。
下面有 m m m 行, o p t opt opt 表示操作标号。
o p t = 1 opt=1 opt=1,则为操作 1 1 1,之后有三个数 l   r   k l~r~k l r k,表示查询 k k k 在区间 [ l , r ] [l,r] [l,r] 的排名。
o p t = 2 opt=2 opt=2,则为操作 2 2 2,之后有三个数 l   r   k l~r~k l r k,表示查询区间 [ l , r ] [l,r] [l,r] 内排名为 k k k 的数。
o p t = 3 opt=3 opt=3,则为操作 3 3 3,之后有两个数 p o s   k pos~k pos k,表示将 p o s pos pos 位置的数修改为 k k k
o p t = 4 opt=4 opt=4,则为操作 4 4 4,之后有三个数 l   r   k l~r~k l r k,表示查询区间 [ l , r ] [l,r] [l,r] k k k 的前驱。
o p t = 5 opt=5 opt=5,则为操作 5 5 5,之后有三个数 l   r   k l~r~k l r k,表示查询区间 [ l , r ] [l,r] [l,r] k k k 的后继。

  • 插入哨兵之后查询小于x的数字数目时要-1
  • 排名需要算自己+1
  • 二分查找时注意写法
  • 注意修改传参的时候需要引用,查找大小和前驱时要使用正常传入

**注意:**巨大常数,需要O2优化

/// author: jixiang 2022.4.4
#include<bits/stdc++.h>
#define rep(i,j,k) for(int i = (j) ; i <= (k) ; i++)
#define fdow (i,j,k) for (int  i = (j) ; i >= (k) ; i--)
#define debug puts("Debeg!!!!! wake up !!!")
#define Mset(a,v) memset(a,v,sizeof (a))
#define Mcpy(a,v) memcpy(a,v,sizeof (a))
#define mrep(i,j) for(int i=(h[j]);~i;i=ne[i])

using namespace std;
typedef long long ll;
typedef pair<int,int> pii;

const int N = 2e6+9;
const int M = 2e6+9;
const int INF=2147483647;
int n,m;
struct node
{
    int s[2],p,v;
    int siz;
    void init(int _v,int _p)
    {
        p = _p; v = _v;
        siz = 1;
    }
}tr[N];
int L[N],R[N],T[N],idx;
int w[N];

void up(int x)
{
    tr[x].siz = tr[tr[x].s[1]].siz + tr[tr[x].s[0]].siz + 1;
}

void spin(int x)
{
    int y = tr[x].p;
    int z = tr[y].p;
    int k = tr[y].s[1]==x;
    tr[z].s[tr[z].s[1]==y] = x; tr[x].p =z;
    tr[y].s[k]=tr[x].s[k^1]; tr[tr[x].s[k^1]].p=y;
    tr[x].s[k^1]=y; tr[y].p=x;
    up(y); up(x);
}

void splay (int &root,int x,int k)
{
    while (tr[x].p!=k)
    {
        int y = tr[x].p;
        int z = tr[y].p;
        if(z!=k)
        {
            if((tr[y].s[1]==x) ^ (tr[z].s[1]==y))spin(x);
            else spin(y);
        }
       spin(x);
    }
    if(k==0)root=x;
}

void insert(int &root,int v)
{
    int u = root; int p =0;
    while (u) p= u,u=tr[u].s[v>tr[u].v];
    u=++idx;
    if(p) tr[p].s[v>tr[p].v] = u;
    tr[u].init(v,p);
    splay(root,u,0);
}

int get_k(int root,int v)
{
    int u =root;
    int res= 0;
    while (u)
    {
        if(tr[u].v<v) res+= tr[tr[u].s[0]].siz + 1 , u=tr[u].s[1];
        else u=tr[u].s[0];
    }
    return res;
}

void update(int &root,int x,int y)
{
    int u = root;
    while (u)
    {
        if(tr[u].v == x) break;
        if(tr[u].v < x) u=tr[u].s[1];
        else u=tr[u].s[0];
    }
    splay(root,u,0);
    /// 找到前驱和后继,然后删除后继左子树
    int l,r;
    l=tr[u].s[0];
    r=tr[u].s[1];
    while (tr[l].s[1]) l=tr[l].s[1];
    while (tr[r].s[0]) r=tr[r].s[0];
    splay (root,l,0);
    splay(root,r,l);
    tr[r].s[0] = 0;
    up(r); up(l);
    insert(root,y);
}

void build(int u,int a,int b)
{
    L[u]=a; R[u]=b;
    insert(T[u],INF); insert(T[u],-INF);
    rep(i,a,b) insert(T[u],w[i]);
    if(a==b)return ;
    int mid = a+b >>1;
    build(u<<1,a,mid);
    build(u<<1|1,mid+1,b);
}

int query(int u,int a,int b,int x)
{
    if(L[u]>=a && R[u]<=b) return get_k(T[u],x)-1;
    int mid = L[u]+R[u] >>1;
    int res = 0;
    if(a<=mid) res+= query(u<<1,a,b,x);
    if(b>mid) res+= query(u<<1|1,a,b,x);
    return res;
}

void change (int u,int p,int x)
{
    update(T[u],w[p],x);
    if(L[u]==R[u]) return;
    int mid = L[u]+R[u] >>1;
    if(p<=mid)change(u<<1,p,x);
    else change(u<<1|1,p,x);
}

int get_pre(int root,int v)
{
    int u = root;
    int res= -INF;
    while (u)
    {
        if(tr[u].v<v) res=max(res,tr[u].v),u=tr[u].s[1];
        else u=tr[u].s[0];
    }
    return res;
}

int get_suc(int root,int v)
{
    int u = root;
    int res= INF;
    while (u)
    {
        if(tr[u].v > v) res= min(res,tr[u].v),u=tr[u].s[0];
        else u=tr[u].s[1];
    }
    return res;
}

int query_pre(int u,int a,int b,int x)
{
    if(L[u]>=a && R[u]<=b) return get_pre(T[u],x);
    int mid = L[u]+R[u] >>1;
    int res = -INF;
    if(a<=mid) res=max(res,query_pre(u<<1,a,b,x));
    if(b>mid) res=max(res,query_pre(u<<1|1,a,b,x));
    return res;
}

int query_suc(int u,int a,int b,int x)
{
    if(L[u]>=a && R[u]<=b) return get_suc(T[u],x);
    int mid = L[u]+R[u] >>1;
    int res = INF;
    if(a<=mid) res=min(res,query_suc(u<<1,a,b,x));
    if(b>mid) res=min(res,query_suc(u<<1|1,a,b,x));
    return res;
}

int main()
{
    read(n); read(m);
    rep(i,1,n)read(w[i]);
    build (1,1,n);
    while(m--)
    {
        int op,a,b,k;
        read(op);
        if(op==1)
        {
            read(a); read(b); read(k);
            wri(query(1,a,b,k)+1);puts("");
            /// 注意排名选自己
        }

        if(op==2)
        {
            read(a); read(b); read(k);
            int l = 0;
            int r = 1e8;
            while (l<r)
            {
                int mid = l+r+1 >>1;
                if(query(1,a,b,mid)+1 <= k)l=mid;
                else r=mid-1;
            }
            wri(l);puts("");
        }

        if(op==3)
        {
            read(a); read(k);
            change(1,a,k);
            w[a]=k;
        }

        if(op==4)
        {
            read(a); read(b); read(k);
            wri(query_pre(1,a,b,k)); puts("");
        }

        if(op==5)
        {
            read(a); read(b); read(k);
            wri(query_suc(1,a,b,k)); puts("");
        }
    }

    return 0;
}

三,权值线段树套线段树(整体二分)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流苏贺风

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值