可持久化线段树基础
这个算法是很多算法的基础
前置知识:线段树基础
动态开点线段树
这个东西的思想是:结点只有在有需要的时候才被创建。
我们进行递归时如果某个节点不存在就新建一个
操作有点像 \(\text{Treap}\) 的新建操作
这样每次操作最多新建 \(\text{log}\) 个节点
空间复杂度 \(O(n\log n)\to O(m\log m)\)
其中 \(n\) 为值域,\(m\) 为操作数
例题 P2781
顺便说一句如果你死调 40 可能是宏定义的奇妙 bug
pushdown 的时候要没有对应节点就自己建一个
#include<bits/stdc++.h>
#define size(l,r) (ll)((r)-(l)+1)
#define Debug(i,l,r) printf("%d(%d-%d) sum=%lld tag=%lld\n",i,l,r,k[i],tag[i])
using namespace std;
typedef long long ll;
const int N=300009;
ll k[N],tag[N];
int cnt,ls[N],rs[N];
class T{
public:
int root=0;
void modify(int &i,int l,int r,int L,int R,ll d){
if(!i)i=++cnt;
if(L==l&&R==r){tag[i]+=d,k[i]+=size(l,r)*d;return;}
pushdown(i,l,r);
int mid=(l+r)>>1;
if(R<=mid)modify(ls[i],l,mid,L,R,d);
else if(L>mid)modify(rs[i],mid+1,r,L,R,d);
else modify(ls[i],l,mid,L,mid,d),modify(rs[i],mid+1,r,mid+1,R,d);
k[i]=k[ls[i]]+k[rs[i]];
}
ll query(int i,int l,int r,int L,int R){
if(!i)return 0;
if(L==l&&R==r)return k[i];
pushdown(i,l,r);
int mid=(l+r)>>1;
if(R<=mid)return query(ls[i],l,mid,L,R);
else if(L>mid)return query(rs[i],mid+1,r,L,R);
else return query(ls[i],l,mid,L,mid)+query(rs[i],mid+1,r,mid+1,R);
}
private:
void pushdown(int i,int l,int r){
if(l==r){tag[i]=0;return;}
if(!tag[i])return;
if(!ls[i])ls[i]=++cnt;
if(!rs[i])rs[i]=++cnt;
tag[ls[i]]+=tag[i],tag[rs[i]]+=tag[i];
int mid=(l+r)>>1;
k[ls[i]]+=size(l,mid)*tag[i],k[rs[i]]+=size(mid+1,r)*tag[i],tag[i]=0;
}
}t;
int main(){
int n,m,l,r,op;
ll d;
scanf("%d%d",&n,&m);
while(m--){
scanf("%d%d%d",&op,&l,&r);
if(op==1)scanf("%lld",&d),t.modify(t.root,1,n,l,r,d);
else printf("%lld\n",t.query(t.root,1,n,l,r));
}
return 0;
}
权值线段树
权值线段树是以值域为维护对象的线段树
比如说普通平衡树这道题
我们记录的其实是每个点的数字数量
插入就将对应的点加一,删除同理减一
我们发现其中的前驱后继都可以用排名和反排名来解决
排名我们有天生求和优势
反排名二分搞一下就行了
至于值域,动态开点!
这样就可以支持在线了
但他的空间复杂度和正经平衡树相比还是略逊一些
像加强版会被卡空间
码量堪比 \(\text{01Trie}\) 的赛博代码
#include<bits/stdc++.h>
using namespace std;
const int N=2200005,n=10000009;
int k[N],ls[N],rs[N],cnt;
struct T{
int root=0;
void modify(int &i,int l,int r,int d,int op){
if(!i)i=++cnt;
if(l==r){k[i]+=op;return;}
int mid=(l+r)>>1;
if(d<=mid) modify(ls[i],l,mid,d,op); else modify(rs[i],mid+1,r,d,op);
k[i]=k[ls[i]]+k[rs[i]];
}
int rank(int i,int l,int r,int L,int R){
if(!i)return 0;
if(l==L&&r==R)return k[i];
int mid=(l+r)>>1;
if(R<=mid) return rank(ls[i],l,mid,L,R); else if(L>mid) return rank(rs[i],mid+1,r,L,R);
return rank(ls[i],l,mid,L,mid)+rank(rs[i],mid+1,r,mid+1,R);
}
int kth(int d){
int i=root,l=-n,r=n,mid;
while(l<r){
mid=(l+r)>>1;
if(k[ls[i]]>=d) i=ls[i],r=mid; else d-=k[ls[i]],i=rs[i],l=mid+1;
}
return l;
}
}t;
int main(){
int m,op,x;
scanf("%d",&m);
while(m--){
scanf("%d%d",&op,&x);
switch(op){
case 1:t.modify(t.root,-n,n,x,1);break;
case 2:t.modify(t.root,-n,n,x,-1);break;
case 3:printf("%d\n",t.rank(t.root,-n,n,-n,x-1)+1);break;
case 4:printf("%d\n",t.kth(x));break;
case 5:printf("%d\n",t.kth(t.rank(t.root,-n,n,-n,x-1)));break;
case 6:printf("%d\n",t.kth(t.rank(t.root,-n,n,-n,x)+1));break;
}
}
return 0;
}
可持久化线段树
P3834 静态区间第 k 小
这题用到上面两点
把数列拆分成 \([1,1][1,2]\dots[1,n]\)
对每个子区间建一棵权值线段树
第 \(k\) 小就是我们前面的排名
所以采用可持久化方法
既然每次都是单修
那我们可以考虑在原来的基础上新建一条链
这个显然是动态开点的
但我们初始的树不能动态开点,因为这样我们会在建链的时候发现缺少对应节点
那这样我们就只能离散化了
处理查询的时候我们把两个对应的排名相减
二分去搞即可
空间应当是 \(O(4n+n\log n)=44e5\)
注意修改操作到了递归边界时需特殊处理!
#include<bits/stdc++.h>
using namespace std;
const int N=4400000,M=200001;
int len,tmp[M],cnt,k[N],ls[N],rs[N],root[M];
vector<int> busket;
struct T{
void build(int &i=root[0],int l=1,int r=len){
i=++cnt;
if(l==r)return;
int mid=(l+r)>>1;
build(ls[i],l,mid),build(rs[i],mid+1,r);
}
void modify(int &i,int I,int l,int r,int d){
i=++cnt;
if(l==r){k[i]=k[I]+1;return;}
int mid=(l+r)>>1;
if(d<=mid) modify(ls[i],ls[I],l,mid,d),rs[i]=rs[I]; else modify(rs[i],rs[I],mid+1,r,d),ls[i]=ls[I];
k[i]=k[ls[i]]+k[rs[i]];
}
int query(int x,int y,int l,int r,int d){
if(l==r)return l;
int mid=(l+r)>>1;
if(d<=k[ls[y]]-k[ls[x]]) return query(ls[x],ls[y],l,mid,d); else return query(rs[x],rs[y],mid+1,r,d-k[ls[y]]+k[ls[x]]);
}
}t;
int main(){
int tlen,n,T,l,r,d;
scanf("%d%d",&n,&T);
for(int i=1;i<=n;i++)scanf("%d",tmp+i),busket.push_back(tmp[i]);
sort(busket.begin(),busket.end());
tlen=unique(busket.begin(),busket.end())-busket.begin(),len=tlen+1,t.build();
for(int i=1;i<=n;i++)tmp[i]=lower_bound(busket.begin(),busket.begin()+tlen,tmp[i])-busket.begin()+1,t.modify(root[i],root[i-1],1,len,tmp[i]);
while(T--)scanf("%d%d%d",&l,&r,&d),printf("%d\n",busket[t.query(root[l-1],root[r],1,len,d)-1]);
}
带修改版本P2617
全名:树状数组套动态开点权值线段树
普通的主席树之所以不能带修是因为修改一个数会至多影响 \(n\) 棵树
极限复杂度会被卡到 \(O(n^2\log n)\)
而我们干脆抛弃主席树的结构
直接 \(n\) 颗动态开点
但是我们此时每棵树的表示范围变为 \([x-lowbit+1,x]\)
然后查询和修改就直接和树状数组类似
这样每个节点的修改就只会影响 \(log\) 颗线段树
本题需要离散化和离线否则卡不过空间
时空复杂度 \(O(n\log^2n)\)
推荐大数组直接 \(3\times10^7\)
#include<bits/stdc++.h>
using namespace std;
const int N=30000000,M=100002,Q=200002;
int cnt,ls[N],rs[N],k[N],tmp[M],length;
vector<int> busket,ADD,MINUS;
struct T{
int root;
void modify(int &i,int l,int r,int d,int op){
if(!i)i=++cnt;
if(l==r){k[i]+=op;return;}
int mid=(l+r)>>1;
if(d<=mid)modify(ls[i],l,mid,d,op); else modify(rs[i],mid+1,r,d,op);
k[i]=k[ls[i]]+k[rs[i]];
}
}t[M];
struct Question{
bool ismodify;
int l,r,d;
}q[M];
int a[M];
int main(){
int n,m,l,r,tot,l1,l2;
char op[3];
scanf("%d%d",&n,&m),busket.push_back(INT_MIN);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),busket.push_back(a[i]);
for(int i=1;i<=m;i++){
scanf("%s",op),q[i].ismodify=(op[0]=='C');
if(q[i].ismodify) scanf("%d%d",&q[i].l,&q[i].d),busket.push_back(q[i].d);
else scanf("%d%d%d",&q[i].l,&q[i].r,&q[i].d);
}
sort(busket.begin(),busket.end()),length=unique(busket.begin(),busket.end())-busket.begin();
for(int i=1;i<=n;i++) a[i]=lower_bound(busket.begin(),busket.begin()+length,a[i])-busket.begin();
for(int i=1;i<=m;i++) if(q[i].ismodify) q[i].d=lower_bound(busket.begin(),busket.begin()+length,q[i].d)-busket.begin();
--length;
for(int i=1;i<=n;i++) for(int j=i;j<=n;j+=(j&-j)) t[j].modify(t[j].root,1,length,a[i],1);
for(int it=1;it<=m;it++){
if(q[it].ismodify){
for(int i=q[it].l;i<=n;i+=(i&-i)) t[i].modify(t[i].root,1,length,a[q[it].l],-1),t[i].modify(t[i].root,1,length,q[it].d,1);
a[q[it].l]=q[it].d;
}else{
ADD.clear(),MINUS.clear(),l=1,r=length,l1=0,l2=0;
for(int i=q[it].r;i>0;i-=(i&-i))ADD.push_back(t[i].root),l1++;
for(int i=q[it].l-1;i>0;i-=(i&-i))MINUS.push_back(t[i].root),l2++;
while(l<r){
tot=0;
for(int i:ADD)tot+=k[ls[i]];
for(int i:MINUS)tot-=k[ls[i]];
// printf("test %d[%d-%d] rank %d need %d\n",busket[(l+r)>>1],busket[l],busket[r],tot,q[it].d);
if(tot>=q[it].d){
for(int i=0;i<l1;i++)ADD[i]=ls[ADD[i]];
for(int i=0;i<l2;i++)MINUS[i]=ls[MINUS[i]];
r=(l+r)>>1;
}else{
q[it].d-=tot;
for(int i=0;i<l1;i++)ADD[i]=rs[ADD[i]];
for(int i=0;i<l2;i++)MINUS[i]=rs[MINUS[i]];
l=(l+r)>>1,l++;
}
}
printf("%d\n",busket[l]);
}
}
return 0;
}

浙公网安备 33010602011771号