线段树复习

线段树复习

线段树合并

线段树合并是在权值线段数上操作的,类似于主席树,一般会在每一个点开一个权值线段树,然后统计答案的时候合并线段树。

\(1.雨天的尾巴\)

题意:首先村落里的一共有 \(n\) 座房屋,并形成一个树状结构。然后救济粮分 \(m\) 次发放,每次选择两个房屋 \((x,y)\)然后对于 \(x\)\(y\) 的路径上(含 \(x\)\(y\))每座房子里发放一袋 \(z\) 类型的救济粮。

然后深绘里想知道,当所有的救济粮发放完毕后,每座房子里存放的最多的是哪种救济粮。

Sol:每个节点建一个权值线段树,然后对于一次救济粮的发放,利用树上差分的思想,在\(u,v\)\(+1\),在\(LCA(u,v)\)\(fa[LCA(u,v)]\)\(-1\),最后自低向上合并线段数计算答案即可。权值线段树一般会维护一个数的个数\(cnt\)量,在判断大小的时候基本上通过\(cnt\)去比较。

 #include <bits/stdc++.h>
 #define sc(x) scanf("%d",&x)
 #define ll long long
 #define ls rt<<1
 #define rs rt<<1|1
 #define endl '\n'
 #define pb push_back
 using namespace std;
 const int N=1e5+10,M=5e5+10;
 const int inf=1e5;
 struct node
 {
      int l,r;
      int cnt;
 }tr[N*50];
 int fa[N][21],dep[N],head[N],tot,idx,root[N],ans[N];
 struct edges
 {
      int v,nxt;
 }e[N*2];
 void add(int u,int v)
 {
      e[tot]={v,head[u]},head[u]=tot++;
 }
 void dfs(int u,int f,int step)
 {
      dep[u]=step;
      for(int i=1;i<=20;i++)
      fa[u][i]=fa[fa[u][i-1]][i-1];
      for(int i=head[u];~i;i=e[i].nxt)
      {
           int v=e[i].v;
           if(v==f) continue;
           fa[v][0]=u;
           dfs(v,u,step+1);
      }
 }
 int LCA(int u,int v)
 {
      if(dep[u]<dep[v]) swap(u,v);
      for(int i=20;i>=0;i--)
      if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
      if(u==v) return u;
      for(int i=20;i>=0;i--)
      {
          if(fa[u][i]!=fa[v][i]) 
          u=fa[u][i],v=fa[v][i];
      }
      return fa[u][0];
 }
 void update(int &rt,int l,int r,int p,int k)
 {
      if(!rt) rt=++idx;
      if(l==r) 
      {
           tr[rt].cnt+=k;
           return;
      }
      int mid=l+r>>1;
      if(p<=mid) update(tr[rt].l,l,mid,p,k);
      else update(tr[rt].r,mid+1,r,p,k);
      tr[rt].cnt=max(tr[tr[rt].l].cnt,tr[tr[rt].r].cnt);

 }
 int merge(int u ,int v)  //把v合并到u上
 {
      if(!u||!v) return u|v;
      tr[u].l=merge(tr[u].l,tr[v].l);
      tr[u].r=merge(tr[u].r,tr[v].r);
      if(!tr[u].l&&!tr[u].r) //如果是叶子节点,直接合并节点上的信息
      {
          tr[u].cnt+=tr[v].cnt;
          return u;
      }
      tr[u].cnt=max(tr[tr[u].l].cnt,tr[tr[u].r].cnt);
      return u;
 }
 int query(int rt,int l,int r)
 {
      if(l==r) return l;
      int mid=l+r>>1;
      if(tr[tr[rt].l].cnt>=tr[tr[rt].r].cnt) return query(tr[rt].l,l,mid);
      else return query(tr[rt].r,mid+1,r);

 }
 void DFS(int u,int f)
 {
      for(int i=head[u];~i;i=e[i].nxt)
      {
           int v=e[i].v;
           if(v==f) continue;
           DFS(v,u);
           root[u]=merge(root[u],root[v]);
      }
      if(tr[root[u]].cnt==0) ans[u]=0;
      else ans[u]=query(root[u],1,inf);
 }
 int main()
 {
      memset(head,-1,sizeof head);
    int n,m;
    sc(n),sc(m);;
    for(int i=1;i<n;i++)
    {
         int u,v;
         sc(u),sc(v);
         add(u,v),add(v,u);
    }
    dfs(1,0,1);
    
    for(int i=1;i<=m;i++)
    {
         int x,y,z;
         sc(x),sc(y),sc(z);
         int lca=LCA(x,y);
         //cout<<lca<<endl;
         update(root[x],1,inf,z,1),update(root[y],1,inf,z,1);
         update(root[lca],1,inf,z,-1),update(root[fa[lca][0]],1,inf,z,-1);
    }
    DFS(1,0);
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
 }

\(2.永无乡\)

题意:永无乡包含$ n$ 座岛,编号从 \(1\)\(n\) ,每座岛都有自己的独一无二的重要度,按照重要度可以将这 \(n\) 座岛排名,名次用 \(1\)\(n\) 来表示。某些岛之间由巨大的桥连接,通过桥可以从一个岛到达另一个岛。如果从岛 \(a\) 出发经过若干座(含 \(0\) 座)桥可以 到达岛 \(b\) ,则称岛 \(a\) 和岛 \(b\) 是连通的。

现在有两种操作:

B x y 表示在岛 \(x\) 与岛 \(y\) 之间修建一座新桥。

Q x k 表示询问当前与岛 \(x\) 连通的所有岛中第 \(k\) 重要的是哪座岛,即所有与岛 \(x\) 连通的岛中重要度排名第 \(k\) 小的岛是哪座,请你输出那个岛的编号。

Sol:每个岛建一个权值线段树,然后用并查集维护联通性,如果加入的边的两个岛不在一个集合中,合并权值线段树,查询就查询\(x\)所在连通块的权值线段树即可。

 #include <bits/stdc++.h>
 #define sc(x) scanf("%d",&x)
 #define ll long long
 #define ls rt<<1
 #define rs rt<<1|1
 #define endl '\n'
 #define pb push_back
 using namespace std;
 const int N=1e5+10,M=5e5+10;
 const int inf=1e5;
 int w[N],rev[N],root[N],idx;
 int fa[N];
 int find(int x)
 {
      return x==fa[x]?x:fa[x]=find(fa[x]);
 }
 struct node
 {
      int l,r;
      int cnt;
 }tr[N*50];
 void update(int &rt,int l,int r,int p)
 {
      if(!rt) rt=++idx;
      tr[rt].cnt++;
      if(l==r) return ;
      int mid=l+r>>1;
      if(p<=mid) update(tr[rt].l,l,mid,p);
      else update(tr[rt].r,mid+1,r,p);
 }
int  merge(int u,int v)
 {
      if(!u||!v) return u|v;
      tr[u].l=merge(tr[u].l,tr[v].l);
      tr[u].r=merge(tr[u].r,tr[v].r);
      if(!tr[u].l&&!tr[u].r)
      {
           tr[u].cnt+=tr[v].cnt;
           return u;
      }
      tr[u].cnt=tr[tr[u].l].cnt+tr[tr[u].r].cnt;
      return u;
 }
int query(int rt,int l,int r,int k)
 {
      if(l==r) return l;
      int mid=l+r>>1;
      if(tr[tr[rt].l].cnt>=k) return query(tr[rt].l,l,mid,k);
      else return query(tr[rt].r,mid+1,r,k-tr[tr[rt].l].cnt);
      
 }
 int main()
 {
      int n,m;
      sc(n),sc(m);
      for(int i=1;i<=n;i++) fa[i]=i,sc(w[i]),rev[w[i]]=i;
      for(int i=1;i<=n;i++) update(root[i],1,n,w[i]);
      for(int i=1;i<=m;i++)
      {
           int u,v;
           sc(u),sc(v);
           int x=find(u),y=find(v);
           if(x!=y)
           {
                root[y]=merge(root[y],root[x]);
                fa[x]=y;
           }
      }
      int q;
      sc(q);
      while(q--)
      {
           char op;
           int x,y;
           cin>>op;
           sc(x),sc(y);
           if(op=='B')
           {
               int fx=find(x),fy=find(y);
               if(fx!=fy)
               {
                    merge(root[fy],root[fx]);
                    fa[fx]=fy;
               }
           }
           else 
           {
           	    x=find(x);
           	    if(tr[root[x]].cnt<y) puts("-1");
                else printf("%d\n",rev[query(root[x],1,n,y)]);
           }
      }
      return 0;
 }

线段树分裂

\(1.线段树分裂\)

题意:给出一个可重集 \(a\)(编号为 \(1\)),它支持以下操作:

0 p x y:将可重集 \(p\) 中大于等于 \(x\) 且小于等于 \(y\) 的值放入一个新的可重集中(新可重集编号为从 \(2\) 开始的正整数,是上一次产生的新可重集的编号+1)。

1 p t:将可重集\(t\) 中的数放入可重集 \(p\),且清空可重集 \(t\)(数据保证在此后的操作中不会出现可重集 \(t\))。

2 p x q:在 p 这个可重集中加入 \(x\) 个数字 \(q\)

3 p x y:查询可重集 p 中大于等于 \(x\) 且小于等于 \(y\) 的值的个数。

4 p k:查询在 \(p\) 这个可重集中第 \(k\) 小的数,不存在时输出 -1

Sol:操作0利用线段树分裂,把所在区间的信息新存在一个节点分裂出来然后清零原来的节点。操作1线段树合并。

 #include <bits/stdc++.h>
 #define sc(x) scanf("%d",&x)
 #define ll long long
 #define ls tr[rt].l
 #define rs tr[rt].r
 #define endl '\n'
 #define pb push_back
 using namespace std;
 const int N=2e5+10;
 int root[N],idx,w[N],cnt;
 struct node
 {
      int l,r;
      ll v;
 }tr[N*100];
 void pushup(int rt)
 {
      tr[rt].v=tr[ls].v+tr[rs].v;
 }
 void build(int &rt,int l,int r)
 {
      if(!rt) rt=++idx;
      if(l==r)
      {
           tr[rt].v+=w[l];
           return ;
      }
      int mid=l+r>>1;
      build(tr[rt].l,l,mid);
      build(tr[rt].r,mid+1,r);
     pushup(rt);
 }
 void update(int &rt,int l,int r,int p,int x)
 {
      if(!rt) rt=++idx;
      if(l==r)
      {
           tr[rt].v+=x;
           return;
      }
      int mid=l+r>>1;
      if(p<=mid) update(ls,l,mid,p,x);
      else update(rs,mid+1,r,p,x);
      pushup(rt);
 }
int merge(int u,int v)
 {
      if(!u||!v) return u|v;
      tr[u].l=merge(tr[u].l,tr[v].l);
      tr[u].r=merge(tr[u].r,tr[v].r);
      if(!tr[u].l&&!tr[u].r)
      {
           tr[u].v+=tr[v].v;
           return u;
      }
      pushup(u);
      return u;
 }
 int split(int p,int L,int R,int l,int r)
 {
      int rt=++idx;
      if(l<=L&&R<=r)
      {
           tr[rt]=tr[p];
           tr[p].l=tr[p].r=tr[p].v=0;
           return rt;
      }
      int mid=L+R>>1;
      if(l<=mid) ls=split(tr[p].l,L,mid,l,r);
      if(r>mid) rs=split(tr[p].r, mid+1,R,l,r);
      pushup(rt),pushup(p);
      return rt;
 }
 ll query1(int rt,int L,int R,int l,int r)
 {
      if(l<=L&&R<=r) return tr[rt].v;
      int mid=L+R>>1;
      ll res=0;
      if(l<=mid) res+=query1(ls,L,mid,l,r);
      if(r>mid) res+=query1(rs,mid+1,R,l,r);
      return res;
 }
 int query2(int rt,int l,int r,int k)
 {
      if(l==r) return l;
      int mid=l+r>>1;
      if(tr[ls].v>=k) return query2(ls,l,mid,k);
      else return query2(rs,mid+1,r,k-tr[ls].v);
 }
 int main()
 {
    int n,m;
    sc(n),sc(m);
    for(int i=1;i<=n;i++) sc(w[i]);
    build(root[++cnt],1,n);     
    while(m--)
    {
         int op;
         sc(op);
         if(op==0)
         {
              int p,x,y;
              sc(p),sc(x),sc(y);
              root[++cnt]=split(root[p],1,n,x,y);
         }
         else if(op==1)
         {
              int p,t;
              sc(p),sc(t);
              root[p]=merge(root[p],root[t]);
         }
         else if(op==2)
         {
              int p,x,q;
              sc(p),sc(x),sc(q);
              update(root[p],1,n,q,x);
         }
         else if(op==3)
         {
              int p,x,y;
              sc(p),sc(x),sc(y);
              printf("%lld\n",query1(root[p],1,n,x,y));
         }
         else
         {
              int p,k;
              sc(p),sc(k);
              if(tr[root[p]].v<k) puts("-1");
              else printf("%d\n",query2(root[p],1,n,k));
         }
    }
    return 0;
 }

区间多次排序问题

\(1.ABC237G\)

题意:给定一个长度为\(N\)的排列\(P\)和一个整数\(X\),有\(Q\)次操作,每次操作有三个参数\((C_i,L_i,R_i)\),如果\(G_i=1\),那么就把\([L_i,R_i]\)区间内的数升序排序,反之降序排序,询问最后序列中等于\(X\)的数的位置。

Sol:这种问题一般是将序列中的数和\(X\)比较大小后赋值为\(0\)\(1\)。在本题中建立两个线段树,第一个线段树中,如果\(P_i\)小于\(X\)赋值为\(0\),大于等于\(X\)赋值为\(1\);在第二个线段树中,如果\(P_i\)小于等于\(X\)赋值为\(0\),大于\(X\)赋值为\(1\)。这样每次操作都对两个线段树操作,最后两个线段树中不同的位置就是\(X\)的位置。具体做法就是区间修改,每次把一段区间修改为\(0\)\(1\),记录区间中\(1\)的个数,涉及区间修改,要打tag。

#include <bits/stdc++.h>
#define ls rt<<1
#define rs rt<<1|1
using namespace std;
const int N=5e5+10;
struct Segment_Tree
{
    int l,r;
    int tag,sum;
}tr1[N*4],tr2[N*4];
int a[N];
void pushup(Segment_Tree tr[],int rt)
{
    tr[rt].sum=tr[ls].sum+tr[rs].sum;
}
void build(Segment_Tree tr[],int rt,int l,int r,int x)
{
    tr[rt].l=l,tr[rt].r=r,tr[rt].tag=-1;
    if(l==r)
    {
        if(a[l]>=x) tr[rt].sum=1;
        return; 
    }
    int mid=l+r>>1;
    build(tr,ls,l,mid,x);
    build(tr,rs,mid+1,r,x);
    pushup(tr,rt);
}
void pushdown(Segment_Tree tr[],int rt)
{
    int &tag=tr[rt].tag;
    if(tag!=-1)
    {
        tr[ls].sum=(tr[ls].r-tr[ls].l+1)*tag;
        tr[rs].sum=(tr[rs].r-tr[rs].l+1)*tag;
        tr[ls].tag=tr[rs].tag=tag;
        tag=-1;
    }
}
void update(Segment_Tree tr[],int rt,int l,int r,int x)
{
    int L=tr[rt].l,R=tr[rt].r;
    if(l<=L&&R<=r)
    {
        tr[rt].sum=(R-L+1)*x;
        tr[rt].tag=x;
        return;
    }
    pushdown(tr,rt);
    int mid=L+R>>1;
    if(l<=mid) update(tr,ls,l,r,x);
    if(r>mid) update(tr,rs,l,r,x);
    pushup(tr,rt);
}
int query(Segment_Tree tr[],int rt,int l,int r)
{
    int L=tr[rt].l,R=tr[rt].r;
    int res=0;
    if(l<=L&&R<=r)
        return tr[rt].sum;   
    
    pushdown(tr,rt);
    int mid=L+R>>1;
    if(l<=mid) res+=query(tr,ls,l,r);
    if(r>mid) res+=query(tr,rs,l,r);
    return res;
}
void sort(Segment_Tree tr[],int c,int l,int r)
{
    int s=query(tr,1,l,r);
    if(c==1)//降序
    {
        update(tr,1,l,r-s,0);
        update(tr,1,r-s+1,r,1);
    }else
    {
        update(tr,1,l,l+s-1,1);
        update(tr,1,l+s,r,0);
    }
}
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int n,q,x;
    cin>>n>>q>>x;
    for(int i=1;i<=n;i++) cin>>a[i];
    build(tr1,1,1,n,x),build(tr2,1,1,n,x+1);
    while(q--)
    {
        int c,l,r;
        cin>>c>>l>>r;
        sort(tr1,c,l,r),sort(tr2,c,l,r);
    }
    for(int i=1;i<=n;i++)
        if(query(tr1,1,i,i)!=query(tr2,1,i,i))
        {
            cout<<i<<'\n';
            return 0;
        }

}

\(2.[HEOI2016/TJOI2016]排序\)

题意:在 \(2016\)年,佳媛姐姐喜欢上了数字序列。因而她经常研究关于序列的一些奇奇怪怪的问题,现在她在研究一个难题,需要你来帮助她。

这个难题是这样子的:给出一个 \(1\)\(n\)的排列,现在对这个排列序列进行 \(m\)次局部排序,排序分为两种:

  • 0 l r 表示将区间 \([l,r]\) 的数字升序排序
  • 1 l r 表示将区间 \([l,r]\)的数字降序排序

注意,这里是对下标在区间 \([l,r]\)内的数排序。
最后询问第 \(q\) 位置上的数字。

Sol:

  • 做法1:离线查询,二分答案。具体就是二分\(q\)位上的答案是什么,假设位\(mid\),那么现在问题就和上面那个题是一样的,但此时我们不需要开两个线段树,开一个和第一个线段树一样的,那么最后判断就是如果第\(p\)位为\(0\),说明\(mid\)就大了,否则\(mid\)就是小了。

     #include <bits/stdc++.h>
     #define sc(x) scanf("%d",&x)
     #define ll long long
     #define ls rt<<1
     #define rs rt<<1|1
     #define endl '\n'
     #define pb push_back
     using namespace std;
     const int N=1e5+10;
    int n,m;
    int q;
    struct Q
    {
         int op,l,r;
    }p[N];
    struct node
    {
         int l,r;
         int lazy,cnt;
    }tr[N*4];
    int a[N];
    
    void build(int rt,int l,int r,int x)
    {
         tr[rt]={l,r};
         if(l==r)
         {
              tr[rt].cnt=a[l]>=x;
              tr[rt].lazy=0;
              return;
         }
         int mid=l+r>>1;
         build(ls,l,mid,x);
         build(rs,mid+1,r,x);
         tr[rt].cnt=tr[ls].cnt+tr[rs].cnt;
         tr[rt].lazy=0;
    
    }
    void pushdown(int rt)
    {
         if(tr[rt].lazy)
         {
              if(tr[rt].lazy==-1)
              {
                   tr[ls].cnt=tr[rs].cnt=0;
                   tr[ls].lazy=tr[rs].lazy=-1;
                   tr[rt].lazy=0;
              }
              else
              {
                   tr[ls].cnt=tr[ls].r-tr[ls].l+1;
                   tr[rs].cnt=tr[rs].r-tr[rs].l+1;
                   tr[ls].lazy=tr[rs].lazy=1;
                   tr[rt].lazy=0;
                }
         }
    }
    void update(int rt,int l,int r,int ql,int qr,int k)
    {
         if(ql<=l&&r<=qr)
         {
              tr[rt].cnt=k*(r-l+1);
              tr[rt].lazy=k?1:-1;
              return;
         }
         if(ql>r||qr<l) return ;
         pushdown(rt);
         int mid=l+r>>1;
         if(ql<=mid) update(ls,l,mid,ql,qr,k);
         if(qr>mid) update(rs,mid+1,r,ql,qr,k);
         tr[rt].cnt=tr[ls].cnt+tr[rs].cnt;
    }
    int query(int rt,int l,int r,int ql,int qr)
    {
         if(ql<=l&&r<=qr)
         {
              return tr[rt].cnt;
         }
         pushdown(rt);
         int res=0;
         int mid=l+r>>1;
         if(ql<=mid) res+=query(ls,l,mid,ql,qr);
         if(qr>mid) res+=query(rs,mid+1,r,ql,qr);
         return res;
    }
    int find(int rt,int l,int r,int pos)
    {
         if(l==r) return tr[rt].cnt;
         pushdown(rt);
         int mid=l+r>>1;
         if(pos<=mid) return find(ls,l,mid,pos);
         else return find(rs,mid+1,r,pos);
    
    }
    int check(int x)
    {
         build(1,1,n,x);
         for(int i=1;i<=m;i++)
         {
              int L=p[i].l,R=p[i].r,op=p[i].op;
              int c1=query(1,1,n,L,R);
              if(op==0)
              {
                   update(1,1,n,R-c1+1,R,1);
                   update(1,1,n,L,R-c1,0);
              }
              else
              {
              	update(1,1,n,L,L+c1-1,1);
                   update(1,1,n,L+c1,R,0);
              }
         }
         return find(1,1,n,q);
    }
    int main()
    {
         sc(n),sc(m);
         for(int i=1;i<=n;i++) sc(a[i]);
         for(int i=1;i<=m;i++)
         sc(p[i].op),sc(p[i].l),sc(p[i].r);
         sc(q);
         int l=1,r=n;
         int ans=0;
         while(l<=r)
         {
              int mid=l+r>>1;
              if(check(mid)) ans=mid,l=mid+1;
              else r=mid-1;
         }
         cout<<ans<<endl;
         return 0;
    }
    
    • 做法2:可以在线操作,同时可以查询出排序后的所有位置的数是什么。太牛了,一点都不会,用到线段树分裂和合并。

      #include <bits/stdc++.h>
      #define lll __in128
      #define pb push_back
      #define endl '\n'
      #define sc(x) scanf("%d",&x)
      #define scl(x) scanf("%lld",&x)
      #define fi first
      #define se second
      #define ls tr[rt].l
      #define rs tr[rt].r
      using namespace std;
      const int N=1e5+10;
      set<int>s;
      int root[N*50],idx,cz[N];
      struct node
      {
      	int l,r;
      	int cnt;
      }tr[N*50];
      void insert(int &rt,int l,int r,int p)
      {
           if(!rt) rt=++idx;
      	 tr[rt].cnt++;
      	 if(l==r)
      	 return ;
      	 int mid=l+r>>1;
      	 if(p<=mid) insert(ls,l,mid,p);
      	 else insert(rs,mid+1,r,p);
      }
      int merge(int u,int v)
      {
      	if(!u||!v) return u|v;
      	tr[u].cnt+=tr[v].cnt;
      	tr[u].l=merge(tr[u].l,tr[v].l);
      	tr[u].r=merge(tr[u].r,tr[v].r);
      	return u;
      }
      void split(int &rt,int p,int k,int op)
      {
      	if(tr[p].cnt==k) return;
      	 rt=++idx;
      	tr[rt].cnt=tr[p].cnt-k;
      	tr[p].cnt=k;
      	if(op)
      	{
      		if(k<=tr[tr[p].r].cnt) split(tr[rt].r,tr[p].r,k,op),tr[rt].l=tr[p].l,tr[p].l=0;
      		else split(tr[rt].l,tr[p].l,k-tr[tr[p].r].cnt,op);
      	}
      	else
      	{
      		if(k<=tr[tr[p].l].cnt) split(tr[rt].l,tr[p].l,k,op),tr[rt].r=tr[p].r,tr[p].r=0;
      		else split(tr[rt].r,tr[p].r,k-tr[tr[p].l].cnt,op);
      	}
      }
      auto Split(int p)
      {
      	auto it=s.lower_bound(p);
      	if(*it==p&&it!=s.end()) return it;
      	--it;
      	split(root[p],root[*it],p-*it,cz[p]=cz[*it]);
      	return s.insert(p).first;
      	
      }
      int query(int rt,int l,int r)
      {
      	if(l==r) return l;
      	int mid=l+r>>1;
      	if(ls) return query(ls,l,mid);
      	else return query(rs,mid+1,r);
      }
      int main()
      {
      	int n,m;
      	sc(n),sc(m);
          s.insert(n+1);
      	for(int i=1;i<=n;i++)
      	{
      		int x;
      		sc(x);
      		s.insert(i);
              insert(root[i],0,n,x);          
      	}
      	for(int i=1;i<=m;i++)
      	{
      		int op,l,r;
      		sc(op),sc(l),sc(r);
      		auto itl=Split(l),itr=Split(r+1);
      		for(auto it=++itl;it!=itr;++it) root[l]=merge(root[l],root[*it]);
      		cz[l]=op;
      		s.erase(itl,itr);
      	}
      	int q;
      	sc(q);
      	Split(q),Split(q+1);
      	cout<<query(root[q],0,n)<<endl;
      	return 0;
      }
      
posted @ 2022-03-15 19:20  Arashimu  阅读(24)  评论(0编辑  收藏  举报