主席树小结

https://zybuluo.com/ysner/note/1099145

标签(空格分隔): 主席树


前置技能

  • 线段树
  • 动态开点
  • 标记永久化
  • 离散化

    定义

    主席树=可持久化线段树=函数式线段树

    线段树经过了若干次修改之后,仍然能找到原来某次修改前的线段树的信息的一种数据结构

    建立

    据说最无脑的方法是每修改一次新建一颗主席树???

单点修改:

  • 线段树单点修改只会改变\(log\)个点(一条链),主席树同理。
  • 那为什么要新建一颗主席树呢?新建修改过的链,再同原来的点接起来,这效果不是一样的吗?

区间修改:

  • 如果像单点修改一样新建修改过的点,显然不好也不能\(Pushdown\)
  • 这个问题可以用标记永久化解决

使用

1、静态整体Kth

滑稽吧...sort一遍就好了。

时间复杂度\(O(nlogn)\) 空间复杂度\(O(n)\)

2、动态整体Kth

离散化后开一棵权值线段树,每个位置的值表示这个位置对应的那个数(离散化后的)有多少个,向上维护和;

查询时先查询左子树和sum,比较k和sum的大小:若k<=sum则说明第k小数在左子树中,递归查询左子树;

否则,这个数对应的就是右子树中第k-sum小的数,k-=sum,递归查询右子树。

时间复杂度\(O(nlogn)\) 空间复杂度\(O(n)\)

3、静态区间Kth

对每个点以其前缀开一棵权值线段树,那么任意一段区间均可以表示成为两棵权值线段树作差,即R位置的线段树减去L-1位置上的线段树

每个点开一棵线段树空间复杂度\(O(n^2)\),MLE,考虑到后一个位置相比于前一个位置的更改只有\(logn\)个节点,所以使用主席树

时间复杂度\(O(nlogn)\) 空间复杂度\(O(nlogn)\)

4、动态区间Kth (不会实现!!!)

还是要想办法维护前缀和。如果只是同3、的前缀和的话,就要对前缀和进行\(O(nlogn)\)的单次修改,显然TLE。

这里考虑用树状数组维护前缀和。修改时,可以只修改logn个位置,复杂度\(O(log^2n)\);

查询时,依旧是R位置减去L-1位置,这时候不再是两棵线段树作差,而是log棵线段树与log棵线段树作差;跳的时候,log个节点一起跳到左子树/右子树

时间复杂度\(O(nlog^2n)\) 空间复杂度\(O(nlog^2n)\)

例题

T1

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

求静态区间第K小

查询区间[l,r]第k小?

可以开一棵值域线段树,维护数的个数

把数字离散化之后丢进线段树中去

然后直接在线段树上二分数字

如果k大于当前点左子树的点的个数,那么k减去这个个数,去右子树

否则去左子树

已知第i棵线段树保存前i个数字

那么运用前缀和的方法,把第r棵线段树和第l?1棵线段树作差,得到的就是这个区间内的数。

T2

Luogu3567 [POI2014]KUR-Couriers

每次询问一个区间内有没有一个数出现次数超过一半

一个区间内只会有一个数满足要求

主席树维护数的个数(即sort+unqiue+lower_bound)

L?1和R值域线段树做差,直接树上二分查询

#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=5e5+100;
int tot,L,R,rt[N],n,m;
struct seg{int ls,rs,num;}t[N*30];
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
  if(ch==‘-‘) t=-1,ch=getchar();
  while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
  return x*t;
}
il void Build(re int &x,re int l,re int r)
{
    x=++tot;
    if(l>=r) return;
    re int mid=l+r>>1;
    Build(lson,l,mid);Build(rson,mid+1,r);
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
    t[++tot]=t[x];t[x=tot].num++;
    if(l>=r) return;
    re int mid=l+r>>1;
    if(pos<=mid) Modify(lson,l,mid,pos);
    else Modify(rson,mid+1,r,pos);
}
il int Query(re int A,re int B,re int l,re int r)
{
    if(l>=r) return l;
    re int mx=max(t[t[B].ls].num-t[t[A].ls].num,t[t[B].rs].num-t[t[A].rs].num);
    if(mx*2<=(R-L+1)) return 0;
    re int mid=l+r>>1;
    if(t[t[B].ls].num-t[t[A].ls].num==mx) return Query(t[A].ls,t[B].ls,l,mid);
    else return Query(t[A].rs,t[B].rs,mid+1,r);
}
int main()
{
  n=gi();m=gi();
  Build(rt[0],1,n);
  fp(i,1,n)
  {
      re int x=gi();rt[i]=rt[i-1];
      Modify(rt[i],1,n,x);
  }
  while(m--)
  {
      L=gi(),R=gi();
      printf("%d\n",Query(rt[L-1],rt[R],1,n));
  }
  return 0;
}

T3

Luogu3168 [CQOI2015]任务查询系统

有m个有优先级的任务,在时间区间[l,r]内被计算机运行
n个询问,求第x秒正在运行的前k小的任务优先级之和

把一个任务拆成两个,在l秒时加入,在r+1时减去

然后每个时间开值域的主席树就好了

// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1e5+100;
int tot,L,R,rt[N],id[N],len,ans=1,n,m;
struct seg{int ls,rs,num,sum;}t[N*50];
struct tas
{
    int t,w,f;
    bool operator < (const tas &o) const {return t<o.t;}
}a[N<<1];
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
  if(ch==‘-‘) t=-1,ch=getchar();
  while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
  return x*t;
}
il void Modify(re int &x,re int l,re int r,re int pos,re int k)
{
    t[++tot]=t[x];x=tot;
    t[x].num+=k;t[x].sum+=k*id[pos];
    if(l>=r) return;
    re int mid=l+r>>1;
    if(pos<=mid) Modify(lson,l,mid,pos,k);
    else Modify(rson,mid+1,r,pos,k);
}
il ll Query(re int x,re int l,re int r,re int k)
{
    if(l>=r) return t[x].num?(1ll*t[x].sum/t[x].num*k):0;
    re int mid=l+r>>1;
    if(k<=t[lson].num) return Query(lson,l,mid,k);
    else return t[lson].sum+Query(rson,mid+1,r,k-t[lson].num);
}
int main()
{
  m=gi();n=gi();
  fp(i,1,m)
  {
      re int s=gi(),e=gi(),f=gi();
      a[i*2-1]=(tas){s,f,1};a[i*2]=(tas){e+1,f,-1};id[i]=f;
  }
  sort(a+1,a+2*m+1);sort(id+1,id+1+m);
  len=unique(id+1,id+1+m)-id-1;
  for(re int i=1,j=1;i<=n;i++)
  {
      rt[i]=rt[i-1];
      for(;a[j].t==i;j++)
      {
          re int x=lower_bound(id+1,id+1+len,a[j].w)-id;
          Modify(rt[i],1,len,x,a[j].f);
      }
  }
  fp(i,1,n)
  {
      re int X=gi(),A=gi(),B=gi(),C=gi(),Y;
      Y=(1ll*A*ans+B)%C+1;ans=Query(rt[X],1,len,Y);
      printf("%lld\n",ans);
  }
  return 0;
}

T4

Luogu2839 [国家集训队]middle

求左端点在[a,b]之间,右端点在[c,d]之间的序列中,最大的中位数。

考虑二分答案

Check把小于它的设为?1,大于等于它的设为1

维护三个玩意儿

[a,b]求一个最大后缀子段和

[c,d]求一个最大前缀子段和

[b+1,c?1]求一个

加起来如果大于等于0,那么满足要求,即这个数还可以变大,否则就只能缩小

每个数开一个线段树来做,空间开不下,用主席树即可

Update:

  • get一个新姿势“线段树返回结构体???
  • RE不止是因为空间开炸了???
    // luogu-judger-enable-o2
    #include<iostream>
    #include<cmath>
    #include<cstdio>
    #include<cstdlib>
    #include<cstring>
    #include<algorithm>
    #define lson t[x].ls
    #define rson t[x].rs
    #define re register
    #define il inline
    #define ll long long
    #define fp(i,a,b) for(re int i=a;i<=b;i++)
    #define fq(i,a,b) for(re int i=a;i>=b;i--)
    using namespace std;
    const int N=5e5+100;
    struct seg{int sum,ls,rs,lm,rm;}t[N*10];
    struct ppl
    {
      int id,d;
      bool operator < (const ppl &o) const {return d<o.d;}
    }num[N];
    struct Res{int sum,ls,rs,lm,rm;}r[N*10];
    int n,Q,ans,q[10],a[N],len,A,B,C,D,tot,rt[N];
    il int gi()
    {
      re int x=0,t=1;
      re char ch=getchar();
      while(ch!=‘-‘&&(ch<‘0‘||ch>‘9‘)) ch=getchar();
      if(ch==‘-‘) t=-1,ch=getchar();
      while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
      return x*t;
    }
    il void Update(re int x)
    {
      t[x].lm=max(t[lson].lm,t[lson].sum+t[rson].lm);
      t[x].rm=max(t[rson].rm,t[lson].rm+t[rson].sum);
      t[x].sum=t[lson].sum+t[rson].sum;
    }
    il void Build(re int &x,re int l,re int r)
    {
      x=++tot;
      if(l==r) {t[x].sum=t[x].lm=t[x].rm=1;return;}
      re int mid=l+r>>1;
      Build(lson,l,mid);Build(rson,mid+1,r);
      Update(x);
    }
    il void Modify(re int &x,re int l,re int r,re int pos)
    {
      t[++tot]=t[x];x=tot;
      if(l==r) {t[x].sum=t[x].lm=t[x].rm=-1;return;}
      re int mid=l+r>>1;
      if(pos<=mid) Modify(lson,l,mid,pos);else if(pos>mid)Modify(rson,mid+1,r,pos);
      Update(x);
    }
    il Res Merge(Res x,Res y)
    {
      Res z;
      z.sum=x.sum+y.sum;
      z.lm=max(x.lm,y.lm+x.sum);
      z.rm=max(y.rm,y.sum+x.rm);
      return z;
    }
    il Res Query(re int x,re int l,re int r,re int ql,re int qr)
    {
      if(ql<=l&&r<=qr)
    {
      re Res tmp=(Res){t[x].sum,0,0,t[x].lm,t[x].rm};
      return tmp;
    }
      re int mid=l+r>>1;
      if(qr<=mid) return Query(lson,l,mid,ql,qr);
      else if(ql>mid) return Query(rson,mid+1,r,ql,qr);
      else return Merge(Query(lson,l,mid,ql,qr),Query(rson,mid+1,r,ql,qr));
    }
    il void wri(re int x)
    {
      if(x<0) putchar(‘-‘),x=-x;
      if(x>9) wri(x/10);
      putchar(x%10+48);
    }
    int main()
    {
      n=gi();fp(i,1,n) a[i]=num[i].d=gi(),num[i].id=i;
      sort(a+1,a+1+n);
      len=unique(a+1,a+1+n)-a-1;
      fp(i,1,n) num[i].d=lower_bound(a+1,a+1+len,num[i].d)-a;
      sort(num+1,num+1+n);
      Q=gi();
      Build(rt[1],1,n);
      fp(i,2,n)
    {
      rt[i]=rt[i-1];
      Modify(rt[i],1,n,num[i-1].id);
    }
      while(Q--)
    {
      A=gi();B=gi();C=gi();D=gi();
      q[0]=(A+ans)%n+1,q[1]=(B+ans)%n+1,q[2]=(C+ans)%n+1,q[3]=(D+ans)%n+1;
      sort(q,q+4);A=q[0],B=q[1],C=q[2],D=q[3];
      re int res=0,dat,l=1,r=n;
      while(l<=r)
    {
      re int mid=l+r>>1;
      if(B+1<=C-1) dat=Query(rt[mid],1,n,B+1,C-1).sum;
      else dat=0;
      dat+=Query(rt[mid],1,n,A,B).rm+Query(rt[mid],1,n,C,D).lm;
      if(dat>=0) res=num[mid].d,l=mid+1; else r=mid-1;
    }
      ans=a[res];
      wri(ans);puts("");
    }
      return 0;
    }      

T5

Luogu2633 Count on a tree

问u和v这两个节点间第K小的点权。

主席树还是为值域线段树,维护数的个数(开w和o两个数组就成了,x无必要)

每个点的线段树版本由它的父亲加入它的点权得到

那么每个点的线段树存的就是它到根的所有点的点权

还是树上二分数字,还是线段树作差:

用rt[i]表示i这个点的线段树

那么就是这样作差rt[u]+rt[v]?rt[lca]?rt[fa[lca]]

这样就只包含了u,v路径上所有的点了

// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=1e5+100;
int n,m,o[N],h[N],cnt,w[N],x[N],len,tot,tim,sz[N],son[N],f[N],d[N],ans,rt[N],top[N];
struct Edge{int to,next;}e[N<<1];
struct seg{int ls,rs,num;}t[N*30];
il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
  if(ch==‘-‘) t=-1,ch=getchar();
  while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
  return x*t;
}
il void wri(re int x)
{
  if(x<0) putchar(‘-‘),x=-x;
  if(x>9) wri(x/10);
  putchar(x%10+‘0‘);
}
il void Build(re int &x,re int l,re int r)
{
  x=++tot;
  if(l==r) return;
  re int mid=l+r>>1;
  Build(lson,l,mid);Build(rson,mid+1,r);
}
il void Modify(re int &x,re int fa,re int l,re int r,re int pos)
{
  t[x=++tot]=t[fa];t[x].num++;
  if(l==r) return;
  re int mid=l+r>>1;
  if(pos<=mid) Modify(lson,t[fa].ls,l,mid,pos);else Modify(rson,t[fa].rs,mid+1,r,pos);
}
il void dfs1(re int u,re int fa,re int deep)
{
  sz[u]=1;f[u]=fa;d[u]=deep;
  Modify(rt[u],rt[fa],1,len,x[u]);
  for(re int i=h[u];i+1;i=e[i].next)
    {
      re int v=e[i].to;
      if(v==fa) continue;
      dfs1(v,u,deep+1);
      sz[u]+=sz[v];
      if(sz[son[u]]<sz[v]) son[u]=v;
    }
}
il void dfs2(re int u,re int up)
{
  top[u]=up;
  if(son[u]) dfs2(son[u],up);
  for(re int i=h[u];i+1;i=e[i].next)
    {
      re int v=e[i].to;
      if(v==f[u]||v==son[u]) continue;
      dfs2(v,v);
    }
}
il int get_lca(re int u,re int v)
{
  while(top[u]^top[v])
    {
      if(d[top[u]]<d[top[v]]) swap(u,v);
      u=f[top[u]];
    }
  if(d[u]>d[v]) swap(u,v);
  return u;
}
il int Query(re int A,re int B,re int C,re int D,re int l,re int r,re int k)
{
  if(l==r) return l;
  re int tmp=t[t[A].ls].num+t[t[B].ls].num-t[t[C].ls].num-t[t[D].ls].num,mid=l+r>>1;
  if(k<=tmp) return Query(t[A].ls,t[B].ls,t[C].ls,t[D].ls,l,mid,k);else return Query(t[A].rs,t[B].rs,t[C].rs,t[D].rs,mid+1,r,k-tmp);//swap(k,tmp)???!!!!!
}
int main()
{
  memset(h,-1,sizeof(h));
  n=gi();m=gi();
  fp(i,1,n) o[i]=w[i]=gi();sort(o+1,o+1+n);///where is sort???
  len=unique(o+1,o+1+n)-o-1;
  fp(i,1,n) x[i]=lower_bound(o+1,o+1+len,w[i])-o;///why the last letter is x???
  fp(i,1,n-1)
    {
      re int u=gi(),v=gi();
      add(u,v);add(v,u);
    }
  Build(rt[0],1,len);
  dfs1(1,0,1);dfs2(1,1);
  while(m--)
    {
      re int u=gi()^ans,v=gi(),lca=get_lca(u,v),k=gi();
      ans=o[Query(rt[u],rt[v],rt[lca],rt[f[lca]],1,len,k)];
      wri(ans);puts("");
    }
  return 0;
}

T6

Luogu3302 [SDOI2013]森林

 1.询问x-y这条链上点权的第k小。保证x,y在同一个连通块里。
 2.连接x,y两点。保证x,y在不同的连通块里。
 

操作一和count on a tree一样,现在考虑操作二怎么处理。

题目中多次提到联通块,这意味者什么?

意味着加边过程实际上是联通块合并的过程

我们可以先dfs遍历所有点,给每个点标上其所属的联通块。

当连边时,我们把联通块合并(当然要用启发式合并啦),让大的联通块吞并小的联通块时,我们要改变每个点的所属,改变它的父亲,改边点的层数,改变联通块的大小(就是链剖一开始维护的东西)。

但总不能重新链剖吧?

于是只能用倍增求lca,这样dfs时改变一个点的父亲后就可以更新倍增数组。

Update:

  • 空间一定要多开,保险1e7???

    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #define ll long long
    #define re register
    #define il inline
    #define lson t[x].ls
    #define rson t[x].rs
    #define fp(i,a,b) for(re int i=a;i<=b;i++)
    #define fq(i,a,b) for(re int i=a;i>=b;i--)
    using namespace std;
    const int N=5e5+100;
    int n,m,T,o[N],w[N],h[N],cnt,len,f[N][20],d[N],sz[N],ff[N],tot,rt[N],ans;
    bool vis[N];
    char s[10];
    struct Edge{int to,next;}e[N<<1];
    struct seg{int ls,rs,num;}t[N*20];
    il void add(re int u,re int v){e[++cnt]=(Edge){v,h[u]};h[u]=cnt;}
    il int gi()
    {
      re int x=0,t=1;
      re char ch=getchar();
      while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
      if(ch==‘-‘) t=-1,ch=getchar();
      while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
      return x*t;
    }
    il void wri(re int x)
    {
      if(x<0) putchar(‘-‘),x=-x;
      if(x>9) wri(x/10);
      putchar(x%10+‘0‘);
    }
    il void Modify(re int &x,re int fa,re int l,re int r,re int pos)
    {
      t[x=++tot]=t[fa];t[x].num++;
      if(l==r) return;
      re int mid=l+r>>1;
      if(pos<=mid) Modify(lson,t[fa].ls,l,mid,pos);else Modify(rson,t[fa].rs,mid+1,r,pos);
    }
    il void dfs(re int u,re int fa,re int st)
    {
      d[u]=d[fa]+1;vis[u]=1;f[u][0]=fa;sz[st]++;ff[u]=st;
      fp(i,1,16) f[u][i]=f[f[u][i-1]][i-1];
      Modify(rt[u],rt[f[u][0]],1,len,w[u]);
      for(re int i=h[u];i+1;i=e[i].next)
    {
      re int v=e[i].to;
      if(v==fa) continue;
      dfs(v,u,st);
    }
    }
    il void Build(re int &x,re int l,re int r)
    {
      x=++tot;
      if(l==r) return;
      re int mid=l+r>>1;
      Build(lson,l,mid);Build(rson,mid+1,r);
    }
    il int get_lca(re int u,re int v)
    {
      if(d[u]<d[v]) swap(u,v);
      fq(i,16,0)
    if(d[u]-(1<<i)>=d[v]) u=f[u][i];
      if(u==v) return u;
      fq(i,16,0)
    if(f[u][i]^f[v][i]) u=f[u][i],v=f[v][i];
      return f[u][0];
    }
    il int Query(re int A,re int B,re int C,re int D,re int l,re int r,re int k)
    {
      if(l==r) return l;
      re int tmp=t[t[A].ls].num+t[t[B].ls].num-t[t[C].ls].num-t[t[D].ls].num,mid=l+r>>1;
      if(k<=tmp) return Query(t[A].ls,t[B].ls,t[C].ls,t[D].ls,l,mid,k);else return Query(t[A].rs,t[B].rs,t[C].rs,t[D].rs,mid+1,r,k-tmp);
    }
    int main()
    {
      memset(h,-1,sizeof(h));
      gi();n=gi();m=gi();T=gi();
      fp(i,1,n) o[i]=w[i]=gi(),ff[i]=i;///ff要初始值
      sort(o+1,o+1+n);
      len=unique(o+1,o+1+n)-o-1;
      fp(i,1,n) w[i]=lower_bound(o+1,o+1+len,w[i])-o;
      fp(i,1,m)
    {
      re int u=gi(),v=gi();
      add(u,v);add(v,u);
    }
      Build(rt[0],1,len);
      fp(i,1,n) if(!vis[i]) dfs(i,0,i);
      while(T--)
    {
      scanf("%s",s);
      if(s[0]==‘Q‘)
    {
      re int u=gi()^ans,v=gi()^ans,lca=get_lca(u,v),k=gi()^ans;
      ans=o[Query(rt[u],rt[v],rt[lca],rt[f[lca][0]],1,len,k)];
      wri(ans),puts("");
    }
      else
    {
      re int u=gi()^ans,v=gi()^ans;
      if(sz[ff[u]]<sz[ff[v]]) swap(u,v);
      dfs(v,u,ff[u]);
      add(u,v);add(v,u);
    }
    }
      return 0;
    }

    T8

    Luogu3293 [SCOI2016]美味

一家餐厅有 n 道菜,编号 1...n ,大家对第 i 道菜的评价值为 \(a_i\)(1<=i<=n)。有 m 位顾客,第 i 位顾客的期望值为 \(b_i\),而他的偏好值为 \(x_i\) 。因此,第 i位顾客认为第j 道菜的美味度为\(b_i\bigotimes(a_j+x_i)\).他只能从第 \(l_i\) 道到第 \(r_i\) 道中选择。请你帮助他们找出最美味的菜。

二分\(a_j+x_i\)

针对\(b_i\)从大到小枚举位数,尽可能使大位为1.

这一位填1或0显然都可以缩小答案范围,于是在这个范围内查找看是否存在\(mid-x_i\)这一数即可。显然可以主席树维护。

Update:注意一下边界

// luogu-judger-enable-o2
#include<iostream>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#define ll long long
#define re register
#define il inline
#define lson t[x].ls
#define rson t[x].rs
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=5e5+100,len=1e5;
int n,m,w[N],tot,rt[N],b,l,x,r,ans,f,sz[N];
struct seg{int ls,rs,sz;}t[N*10];
il int gi()
{
  re int x=0,t=1;
  re char ch=getchar();
  while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
  if(ch==‘-‘) t=-1,ch=getchar();
  while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
  return x*t;
}
il void wri(re int x)
{
  if(x<0) putchar(‘-‘),x=-x;
  if(x>9) wri(x/10);
  putchar(x%10+‘0‘);
}
il void Build(re int &x,re int l,re int r)
{
  x=++tot;
  if(l==r) return;
  re int mid=l+r>>1;
  Build(lson,l,mid);Build(rson,mid+1,r);
}
il void Modify(re int &x,re int l,re int r,re int pos)
{
  t[++tot]=t[x];++t[x=tot].sz;
  if(l==r) return;
  re int mid=l+r>>1;
  if(pos<=mid) Modify(lson,l,mid,pos);else Modify(rson,mid+1,r,pos);
}
il int Query(re int A,re int B,re int l,re int r,re int ql,re int qr)
{
  if(ql<=l&&r<=qr) return t[B].sz-t[A].sz;
  re int mid=l+r>>1,res=0;
  if(ql<=mid) res=Query(t[A].ls,t[B].ls,l,mid,ql,qr);
  if(qr>mid) res+=Query(t[A].rs,t[B].rs,mid+1,r,ql,qr);
  return res;
}
int main()
{
  n=gi();m=gi();
  Build(rt[0],1,len);
  fp(i,1,n) w[i]=gi(),rt[i]=rt[i-1],Modify(rt[i],0,len,w[i]);//数据范围这么小,连离散化都不需要。。。
  while(m--)
    {
      ans=0;
      b=gi(),x=gi(),l=gi(),r=gi();re int L,R;
      fq(j,17,0)//别忘了处理最后一位
     {
    if((1<<j)&b) L=ans,R=ans+(1<<j)-1,f=0;//该位填0
    else L=ans+(1<<j),R=ans+(1<<j+1)-1,f=1;//该位填1
    if(!Query(rt[r],rt[l-1],0,len,max(0,L-x),min(R-x,len))) f^=1;//没这数
         ans|=(f<<j);
     }
      wri(ans^b);puts("");
    }
  return 0;
}

T9

Luogu2468 [SDOI2010]粟粟的书架

[题面戳我][https://www.luogu.org/problemnew/show/P2468]

吐槽一句,强行二合一有意思不???

  • 二维的50分,开数组\(sum[i][j][k]\),\(gs[i][j][k]\)分别记录左上点为\((1,1)\),右下点为\((i,j)\)的矩阵中大于等于\(k\)的数的和 和 个数。二分k即可。
  • 一维的50分,开主席树维护页数,每次在主席树里找出足够大的和。(???)

    Update:二维那里,二分完后可能有多余的个数。why?

    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #define ll long long
    #define re register
    #define il inline
    #define lson t[x].ls
    #define rson t[x].rs
    #define fp(i,a,b) for(re int i=a;i<=b;i++)
    #define fq(i,a,b) for(re int i=a;i>=b;i--)
    using namespace std;
    const int N=1050,len=1000;
    int r,c,m,a,gs[210][210][1010],sum[210][210][1010],sm[500005],tot,rt[500005],L,R,h,ans;
    struct seg{int ls,rs,sz,sum;}t[10000000];
    il int gi()
    {
      re int x=0,t=1;
      re char ch=getchar();
      while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
      if(ch==‘-‘) t=-1,ch=getchar();
      while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
      return x*t;
    }
    il void wri(re int x)
    {
      if(x<0) putchar(‘-‘),x=-x;
      if(x>9) wri(x/10);
      putchar(x%10+‘0‘);
    }
    il void Modify(re int &x,re int l,re int r,re int pos)
    {
      t[++tot]=t[x];++t[x=tot].sz;t[x].sum+=pos;
      if(l==r) return;
      re int mid=l+r>>1;
      if(pos<=mid) Modify(lson,l,mid,pos);else Modify(rson,mid+1,r,pos);
    }
    il int Query(re int A,re int B,re int l,re int r)
    {
      if(l==r) return (h+l-1)/l;
      re int mid=l+r>>1,tmp=t[t[A].rs].sum-t[t[B].rs].sum;
      if(tmp>=h) return Query(t[A].rs,t[B].rs,mid+1,r);
      else {h-=tmp;return t[t[A].rs].sz-t[t[B].rs].sz+Query(t[A].ls,t[B].ls,l,mid);}
    }
    int main()
    {
       r=gi();c=gi();m=gi();
       if(r>1)
     {
        fp(i,1,r)
    fp(j,1,c)
    {
    re int x=gi();
    fp(k,1,1000)
      {
        sum[i][j][k]=sum[i-1][j][k]+sum[i][j-1][k]-sum[i-1][j-1][k];
        gs[i][j][k]=gs[i-1][j][k]+gs[i][j-1][k]-gs[i-1][j-1][k];
        if(x>=k) sum[i][j][k]+=x,gs[i][j][k]++;
      }
      }
      while(m--)
      {
        re int l=1,r=1000,ans=1001,x=gi(),y=gi(),xx=gi(),yy=gi(),h=gi();
        while(l<=r)
      {
        re int mid=l+r>>1;
        if(sum[xx][yy][mid]-sum[x-1][yy][mid]-sum[xx][y-1][mid]+sum[x-1][y-1][mid]>=h) ans=mid,l=mid+1;
        else r=mid-1;
      }
    if(ans==1001) {puts("Poor QLW");continue;}
    re int ans1=gs[xx][yy][ans]-gs[x-1][yy][ans]-gs[xx][y-1][ans]+gs[x-1][y-1][ans],ans2=sum[xx][yy][ans]-sum[x-1][yy][ans]-sum[xx][y-1][ans]+sum[x-1][y-1][ans]-h;
    wri(ans1-ans2/ans);puts("");
     }
     }
       else
     {
       fp(i,1,c) a=gi(),rt[i]=rt[i-1],Modify(rt[i],1,1000,a);
       while(m--)
     {
       gi();L=gi();gi();R=gi();h=gi();
       if(t[rt[R]].sum-t[rt[L-1]].sum<h) {puts("Poor QLW");continue;}
       else printf("%d\n",Query(rt[R],rt[L-1],1,1000));
     }
     }
      return 0;
    }

    T10

    Luogu2048 [NOI2010]超级钢琴

    求给定序列中长度为L~R之间的前k大子序列的和

    有一个很暴力的想法,就是把所有合法的状态丢到一个堆里面,然后依次取出最大值。这样的话时间是\(O(n^2logn)\),空间是\(O(n^2)\).

    我们考虑优化这个过程。对于右端点相同的所有合法区间,我们只在堆中保留最大的一个,在取出这一个以后再丢入次大的,取出次大的再丢入次次大的,以此类推。这样可以保证正确性,同时空间被优化到了\(O(n)\)

    现在我们考虑怎么查这个次次次次(这里有若干个次)大值。

    首先把区间和转化成前缀和相减,因为我们固定了右端点,那么最大值就是对应了前缀和中减去的那个值最小。而第k大值刚好对应减去的那个数第k小,同时还要保证在一定区间范围内(L、R的限制)。所以离散前缀和以后主席树即可。

    注意前缀和减去的部分可以是0,所以0也要被离散进去。

    // luogu-judger-enable-o2
    #include<iostream>
    #include<cmath>
    #include<cstring>
    #include<cstdio>
    #include<cstdlib>
    #include<algorithm>
    #include<queue>
    #define ll long long
    #define re register
    #define il inline
    #define lson t[x].ls
    #define rson t[x].rs
    #define fp(i,a,b) for(re int i=a;i<=b;i++)
    #define fq(i,a,b) for(re int i=a;i>=b;i--)
    using namespace std;
    const int N=1e6+100;
    struct seg{int ls,rs,num;}t[N*10];
    struct node
    {
      int x,k;ll w;
      bool operator < (const node &o) const {return w<o.w;}
    };
    priority_queue<node>Q;
    int n,k,L,R,w[N],o[N],len,tot,rt[N];
    ll ans;
    il int gi()
    {
      re int x=0,t=1;
      re char ch=getchar();
      while((ch<‘0‘||ch>‘9‘)&&ch!=‘-‘) ch=getchar();
      if(ch==‘-‘) t=-1,ch=getchar();
      while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-48,ch=getchar();
      return x*t;
    }
    il void Modify(re int &x,re int l,re int r,re int pos)
    {
      t[++tot]=t[x];t[x=tot].num++;
      if(l==r) return;
      re int mid=l+r>>1;
      if(pos<=mid) Modify(lson,l,mid,pos);else Modify(rson,mid+1,r,pos);
    }
    il int Query(re int A,re int B,re int l,re int r,re int k)
    {
      if(l==r) return l;
      re int mid=l+r>>1,tmp=t[t[A].ls].num-t[t[B].ls].num;
      if(k<=tmp) return Query(t[A].ls,t[B].ls,l,mid,k);
      else return Query(t[A].rs,t[B].rs,mid+1,r,k-tmp);
    }
    int main()
    {
      n=gi();k=gi();L=gi();R=gi();++n;
      fp(i,2,n) o[i]=w[i]=gi()+w[i-1];
      sort(o+1,o+1+n);
      len=unique(o+1,o+1+n)-o-1;
      fp(i,1,n) w[i]=lower_bound(o+1,o+1+len,w[i])-o;
      fp(i,1,n) rt[i]=rt[i-1],Modify(rt[i],1,len,w[i]);
      fp(i,L+1,n) Q.push((node){i,1,o[w[i]]-o[Query(rt[i-L],rt[max(0,i-R-1)],1,len,1)]});
      while(k--)
    {
      node tmp=Q.top();Q.pop();
      ans+=tmp.w;
      if(tmp.k==min(R-L+1,tmp.x-L)) continue;
      tmp.k++;
      tmp.w=o[w[tmp.x]]-o[Query(rt[tmp.x-L],rt[max(0,tmp.x-R-1)],1,len,tmp.k)];
      Q.push(tmp);
    }
      printf("%lld\n",ans);
      return 0;
    }

    未完待续

    [1]: https://www.luogu.org/problemnew/show/P2468

原文地址:https://www.cnblogs.com/yanshannan/p/8715535.html

时间: 2024-11-08 21:50:24

主席树小结的相关文章

小结:线段树 &amp; 主席树

概要: 就是用来维护区间信息,然后各种秀智商游戏. 应用: 优化dp.主席树等. 技巧及注意: size值的活用:主席树就是这样来的.支持区间加减,例题和模板:主席树,[BZOJ]1146: [CTSC2008]网络管理Network(树链剖分+线段树套平衡树+二分 / dfs序+树状数组+主席树),[BZOJ]1901: Zju2112 Dynamic Rankings(区间第k小+树状数组套可持久化线段树(主席树)) 01(就是更新和不更新等这种对立操作)情况:我们就要在各个更新的操作中明白

主席树(函数式线段树)学习小结(附手绘讲解图片)

主席树是一种离线数据结构,是由很多棵线段树组成的. 第i棵线段树存的是前i个数的信息: 每一个线段存数字的出现次数(因此建树之前要离散化). 那么n棵线段树不会MLE吗? 当然会了! 但是我们发现第i棵线段树和第i-1棵线段树是非常相似的,有许多结点完全相同,因此可以借用之前的结点,没必要新建结点. 具体建树方法建下图: 序列为 1 3 4 2 那么如果要询问i-j之间数字出现的次数怎么办呢? 因为每一棵线段树的区间都是相同的,所以要求l-r之间的数字的出现次数只要用前r位出现的次数减去前l-1

[bzoj3524][Poi2014]Couriers_主席树

Couriers bzoj-3524 Poi-2014 题目大意:给定n个数的序列,询问区间内是否存在一个在区间内至少出现了(区间长度>>1)次的数.如果有,输出该数,反之输出0. 注释:$1\le n,m\le 5\cdot 10^5$. 想法:主席树裸题. 主流做法就是弄一个Existence数组询问有没有这样的数,然后查询区间中位数即可. 但是可以在query的时候强行查询,因为没有输出0,直接输出即可. 最后,附上丑陋的代码... ... #include <iostream&g

[bzoj1901][Zju2112]Dynamic Rankings_主席树

Dynamic Rankings bzoj-1901 Zju-2112 题目大意:给定一个n个数的序列,m个操作,支持:单点修改:查询区间k小值. 注释:$1\le n,m\le 10^4$. 想法:如果这个教树套树的话,我也没办法. 其实就是借用了树状数组的思想,我们在这里叫它...阉割树状数组把. 具体地,主席树每个节点维护的仍然是前缀权值线段树. 修改的时候将修改的点二进制lowbit分解.在分解的节点的权值线段树上直接修改. 查询时我们将所有区间(左端点-1)都二进制lowbit分解,然

[poj2104]可持久化线段树入门题(主席树)

解题关键:离线求区间第k小,主席树的经典裸题: 对主席树的理解:主席树维护的是一段序列中某个数字出现的次数,所以需要预先离散化,最好使用vector的erase和unique函数,很方便:如果求整段序列的第k小,我们会想到离散化二分和线段树的做法, 而主席树只是保存了序列的前缀和,排序之后,对序列的前缀分别做线段树,具有差分的性质,因此可以求任意区间的第k小,如果主席树维护索引,只需要求出某个数字在主席树中的位置,即为sort之后v中的索引:若要求第k大,建树时反向排序即可 1 #include

【BZOJ 3551】[ONTAK2010] Peaks加强版 Kruskal重构树+树上倍增+主席树

这题真刺激...... I.关于Kruskal重构树,我只能开门了,不过补充一下那玩意还是一棵满二叉树.(看一下内容之前请先进门坐一坐) II.原来只是用树上倍增求Lca,但其实树上倍增是一种方法,Lca只是他的一种应用,他可以搞各种树上问题,树上倍增一般都会用到f数组. |||.我们跑出来dfs序就能在他的上面进行主席树了. IV.别忘了离散. V.他可能不连通,我一开始想到了,但是我觉得出题人可能会是好(S)人(B),但是...... #include <cstdio> #include

[bzoj3932][CQOI2015]任务查询系统-题解[主席树][权值线段树]

Description 最近实验室正在为其管理的超级计算机编制一套任务管理系统,而你被安排完成其中的查询部分.超级计算机中的 任务用三元组(Si,Ei,Pi)描述,(Si,Ei,Pi)表示任务从第Si秒开始,在第Ei秒后结束(第Si秒和Ei秒任务也在运行 ),其优先级为Pi.同一时间可能有多个任务同时执行,它们的优先级可能相同,也可能不同.调度系统会经常向 查询系统询问,第Xi秒正在运行的任务中,优先级最小的Ki个任务(即将任务按照优先级从小到大排序后取前Ki个 )的优先级之和是多少.特别的,如

BZOJ_3207_花神的嘲讽计划1_(Hash+主席树)

描述 http://www.lydsy.com/JudgeOnline/problem.php?id=3207 给出一个长度为\(n\)的串,以及\(m\)个长度为\(k\)的串,求每个长度为\(k\)的串在原串\([x,y]\)区间是否出现过. 分析 这道题要求对比长度为\(k\)的串,于是我们把这些串的Hash值都算出来,问题就转化成了求\([x,y]\)的区间中是否出现过某Hash值. 求区间中某一个值出现了多少次,可以用主席树. p.s. 1.学习了主席树指针的写法,比数组慢好多啊...

[主席树]ZOJ3888 Twelves Monkeys

题意:有n年,其中m年可以乘时光机回到过去,q个询问 下面m行,x,y 表示可以在y年穿越回x年, 保证y>x 下面q个询问, 每个询问有个年份k 问的是k年前面 有多少年可以通过一种以上($\ge 2$)方法穿越回去的, 其中时光机只能用一次 比如案例 9 3 3 9 1 6 1 4 1 6 7 2 如图 对于询问 6这一年:1.穿越回第1年  2.等时间过呀过呀过到第9年,再穿越回第1年 那么第1年就有两种方法可以穿越回去, 同理, 2.3.4年也有同样两种方法(回到1再等时间过呀过 过到2