主席树
主席树
主席树,就是可持久化权值线段树,也叫函数式线段树
引入
考虑如下问题:
给定一个数列,查询其中第k大值
显然,我们可以建一棵权值线段树,直接在上面二分就好了,即对于每个结点,查看它左子树的结点数量是否大于k,设为 \(sum\) 如果 \(sum \ge k\) ,则第k个结点在其左子树中,否则就是其右子树的第 \(k-sum\) 个结点,递归一直到叶子即可。
考虑第二个问题:
给定一个数列,查询前缀第k大值
我们考虑对于每个前缀建一棵权值线段树,重复上述做法,但是这样预处理(即建树)时空复杂度是 \(O(n^2 \log n)\) 的,因为我们要建n棵树。但是我们发现这几棵树每一棵都只比上一棵多一个结点,所以我们考虑用一种更加强大的建树法。
建树(静态主席树)
一棵动态开点的权值线段树,它可能是这样的(左边编号为0-14的完全二叉树,当然,不一定是完全二叉树)
我们发现,假如说我们要下一个前缀在上一个基础上修改12号结点,我们直接像这样重开一条链,剩下部分指回原来没有改的结点就好,因为下面都是完全一样的,从新的根(15)开始便利,就可以找到这棵改完的树了,时空复杂度 \(O(n \log n)\) (其实不离散化的情况下是 \(O(n \log siz)\) ,siz是值域)
具体实现就是建立结点的时候同步遍历上一个版本的树,然后复制一个结点之后改一个分支就好
建树代码:
void build(int &now,int old,int st,int ed,int x)
{
node[++sz]=node[old],ls[sz]=ls[old],rs[sz]=rs[old],now=sz;
if(st==ed) {node[now]++;return;}
if(x<=mid) build(ls[now],ls[old],st,mid,x);
if(x>mid) build(rs[now],rs[old],mid+1,ed,x);
node[now]=node[ls[now]]+node[rs[now]];
}
好了,这就是主席树核心部分了,剩下的查询和普通树一样了
我们发现它维护的实质上是前缀,所以说对于区间操作,只能维护可差分信息
贴个板子题K小数
代码
CODE
#include<bits/stdc++.h>
using namespace std;
#define N 201000
long long n,q,a[N],root[N],p,b,c;
struct SEGMENT_TREE
{
#define mid ((st+ed)>>1)
long long node[N*100],ls[N*100],rs[N*100],sz;
void build(long long &now,long long &old,long long st,long long ed,long long x)
{
node[++sz]=node[old],ls[sz]=ls[old],rs[sz]=rs[old],now=sz;
if(st==ed) {node[now]++;return;}
if(x<=mid) build(ls[now],ls[old],st,mid,x);
if(x>mid) build(rs[now],rs[old],mid+1,ed,x);
node[now]=node[ls[now]]+node[rs[now]];
}
long long find(long long &x,long long &y,long long st,long long ed,long long rk)
{
long long sum=node[ls[y]]-node[ls[x]];
if(st==ed) return st;
if(rk<=sum) return find(ls[x],ls[y],st,mid,rk);
if(rk>sum) return find(rs[x],rs[y],mid+1,ed,rk-sum);
return 0;
}
#undef mid
}s_tree;
int main()
{
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
scanf("%lld%lld",&n,&q);
for(int i=1;i<=n;i++) scanf("%lld",&a[i]),s_tree.build(root[i],root[i-1],1,2e9,a[i]+1e9);
for(int i=1;i<=q;i++)
{
scanf("%lld%lld%lld",&p,&b,&c);
printf("%lld\n",s_tree.find(root[p-1],root[b],1,2e9,c)-(long long)1e9);
}
return 0;
}
进阶操作:区间修改
这个不会很重要,比如二维数点维护线段之类的,标记永久化就好了,和普通线段树永久化差不了太多
动态主席树
依旧是模板题,但是带修,比如 树套树
我们发现主席树不支持修改一个值,因为它对后面都有影响。
考虑在最外层套一个树状数组,它每个结点都是普通线段树,时空复杂度都是 \(O((n+m) \log^2 (n+m))\) ,可以在初始序列用主席树优化建树。这做法虽然空间复杂度劣,但是常数非常小,时间吊打线段树套平衡树之类做法
CODE
#include<bits/stdc++.h>
#define N 1001000
#define L 50010
#define llt long long
using namespace std;
llt n,m,a,b,c,s[L],R[L];char op;
#define mid ((st+ed)>>1)
struct SEGMENT_TREE
{
llt sz,ls[N*30],rs[N*30],node[N*30];
void build(llt &now,llt st,llt ed,llt x,llt o)
{
if(!now) now=++sz;
if(st==ed) {node[now]+=o;return;}
if(x<=mid) build(ls[now],st,mid,x,o);
if(x>mid) build(rs[now],mid+1,ed,x,o);
node[now]=node[ls[now]]+node[rs[now]];
}
}S;
struct PRESIDENT_TREE
{
llt sz,ls[N*30],rs[N*30],node[N*30];
void build(llt &now,llt old,llt st,llt ed,llt x)
{
now=++sz;node[now]=node[old],ls[now]=ls[old],rs[now]=rs[old];
if(st==ed) {node[now]++;return;}
if(x<=mid) build(ls[now],ls[old],st,mid,x);
if(x>mid) build(rs[now],rs[old],mid+1,ed,x);
node[now]=node[ls[now]]+node[rs[now]];
}
}P;
#define tol for(int i=1;i<=la;i++) a[i]=S.ls[a[i]];for(int i=1;i<=lb;i++) b[i]=S.ls[b[i]];
#define tor for(int i=1;i<=la;i++) a[i]=S.rs[a[i]];for(int i=1;i<=lb;i++) b[i]=S.rs[b[i]];
llt find(llt x,llt y,llt* a,llt *b,llt la,llt lb,llt st,llt ed,llt rk)
{
llt sum=P.node[P.ls[x]]-P.node[P.ls[y]];
for(int i=1;i<=la;i++) sum+=S.node[S.ls[a[i]]];
for(int i=1;i<=lb;i++) sum-=S.node[S.ls[b[i]]];
if(st==ed) return st;
if(sum>=rk) {tol return find(P.ls[x],P.ls[y],a,b,la,lb,st,mid,rk);}
else {tor return find(P.rs[x],P.rs[y],a,b,la,lb,mid+1,ed,rk-sum);}
}
llt find_rk(llt x,llt y,llt* a,llt *b,llt la,llt lb,llt st,llt ed,llt num)
{
llt sum=P.node[P.ls[x]]-P.node[P.ls[y]];
for(int i=1;i<=la;i++) sum+=S.node[S.ls[a[i]]];
for(int i=1;i<=lb;i++) sum-=S.node[S.ls[b[i]]];
if(st==ed) return 1;
if(num<=mid) {tol return find_rk(P.ls[x],P.ls[y],a,b,la,lb,st,mid,num);}
else {tor return find_rk(P.rs[x],P.rs[y],a,b,la,lb,mid+1,ed,num)+sum;}
}
#undef mid
struct BIT
{
#define lowbit(x) (x&(-x))
llt root[L],u[L],v[L];
llt check(llt x,llt y,llt rk)
{
if(rk==0) return -2147483647;
if(rk>y-x+1) return 2147483647;
llt p1=0,p2=0;x--;
for(int i=y;i>=1;i-=lowbit(i))
v[++p1]=root[i];
for(int i=x;i>=1;i-=lowbit(i))
u[++p2]=root[i];
return find(R[y],R[x],v,u,p1,p2,-1e8,1e8,rk);
}
llt check_rk(llt x,llt y,llt num)
{
llt p1=0,p2=0;x--;
for(int i=y;i>=1;i-=lowbit(i))
v[++p1]=root[i];
for(int i=x;i>=1;i-=lowbit(i))
u[++p2]=root[i];
return find_rk(R[y],R[x],v,u,p1,p2,-1e8,1e8,num);
}
llt Nxt(llt x,llt y,llt num){return check(x,y,check_rk(x,y,num+1));}
llt Pre(llt x,llt y,llt num){return check(x,y,check_rk(x,y,num)-1);}
void change(llt x,llt is,llt p)
{
for(int i=x;i<=n;i+=lowbit(i))
S.build(root[i],-1e8,1e8,is,p);
}
}bit;
int main()
{
#ifndef ONLINE_JUDGE
freopen("1.in","r",stdin);
freopen("1.out","w",stdout);
#endif
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
scanf("%lld",&s[i]),P.build(R[i],R[i-1],-1e8,1e8,s[i]);
for(int i=1;i<=m;i++)
{
scanf(" %c",&op);
if(op=='1'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.check_rk(a,b,c));}
if(op=='2'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.check(a,b,c));}
if(op=='3'){scanf("%lld%lld",&a,&b);bit.change(a,s[a],-1);s[a]=b;bit.change(a,s[a],1);}
if(op=='4'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.Pre(a,b,c));}
if(op=='5'){scanf("%lld%lld%lld",&a,&b,&c);printf("%lld\n",bit.Nxt(a,b,c));}
}
return 0;
}