zoukankan      html  css  js  c++  java
  • 复习笔记——数据结构

    并查集

    普通并查集

    并查集又快又妙,维护连通性超级方便,千万别忘了它
    有时删边操作逆过来可看成加边,并查集维护

    路径压缩,有时不过脑子就写错了,注意注意!!

    int fa[N];
    int getfa(int x) { return x==fa[x]?x:fa[x]=getfa(fa[x]); }
    void unit(int x,int y) { x=getfa(x); y=getfa(y); fa[x]=y; }
    

    带权并查集

    (getfa) 操作路径压缩过程中顺便更新权值
    一般是维护边权、异或啥的,更新时注意细节


    线段树

    普通线段树

    支持区间加(打tag标记)、覆盖、查询
    一些模板

    int cnt,root,ch[N*2][2];
    ll sum[N*2],tag[N*2];
    
    void update(int x) { sum[x]=sum[ch[x][0]]+sum[ch[x][1]]; }
    void build(int x,int l,int r){
    	if(l==r) { sum[x]=a[l]; return; }
    	int mid=(l+r)>>1;
    	build(ch[x][0]=++cnt,l,mid);
    	build(ch[x][1]=++cnt,mid+1,r);
    	update(x);
    }
    void push(int x,int l,int r,ll c){
    	tag[x]+=c;
    	sum[x]+=c*(r-l+1);
    }
    void pushdown(int x,int l,int r){
    	if(!tag[x]) return;
    	int mid=(l+r)>>1;
    	push(ch[x][0],l,mid,tag[x]);
    	push(ch[x][1],mid+1,r,tag[x]);
    	tag[x]=0;
    }
    void modify(int x,int l,int r,int L,int R,ll c){
    	if(L<=l && r<=R) { push(x,l,r,c); return; }
    	pushdown(x,l,r);
    	int mid=(l+r)>>1;
    	if(L<=mid) modify(ch[x][0],l,mid,L,R,c);
    	if(R>mid) modify(ch[x][1],mid+1,r,L,R,c);
    	update(x);
    }
    ll Sum(int x,int l,int r,int L,int R){
    	if(L<=l && r<=R) return sum[x];
    	pushdown(x,l,r);
    	int mid=(l+r)>>1;
    	ll ret=0;
    	if(L<=mid) ret+=Sum(ch[x][0],l,mid,L,R);
    	if(R>mid) ret+=Sum(ch[x][1],mid+1,r,L,R);
    	return ret;
    }
    

    支持单点修改、最大子段和(维护区间内最大子段和、包含左端点或右端点的最大子段)
    支持区间加乘结合(打两个tag,表示先乘后加,push的时候记得都要更新)
    。。。。。

    李超线段树

    动态维护一些直线(支持插入直线),支持查询 与 (x=x_0) 相交的直线中最上 (or) 最下的一条是谁

    精髓:维护区间最佳直线
    以询问交点纵坐标 (min) 为例
    在某个区间加入一条直线:
    1)当前区间无最佳直线:将该直线设为该区间最佳直线,返回
    2)在当前区间完全>最佳直线:不可能更优,直接返回
    3)在当前区间完全<最佳直线:更优,将最佳直线设为当前这条,返回
    4)区间中有一部分最佳直线更优,一部分当前直线更优:两条中一定有一条更优的部分更小(小于等于区间长度的一半),把大的那条设为当前区间的最佳直线,小的那条往它更优的那半边子树下放,在新的区间中继续操作

    db K[N],B[N];//y=Kx+B
    int cnt,root,ch[M*2][2],mx[M*2];
    void build(int x,int l,int r){
    	mx[x]=0;
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	build(ch[x][0]=++cnt,l,mid);
    	build(ch[x][1]=++cnt,mid+1,r);
    }
    db cal(int a,int x){ return K[a]*x+B[a]; }
    bool better(int a,int b,int x){
    	if(a==0) return false;
    	if(b==0) return true;
    	db ya=cal(a,x),yb=cal(b,x);
    	if(fabs(ya-yb)<eps) return a<b;
    	return ya>yb;
    }
    void ins(int x,int l,int r,int L,int R,int c){
    	if(L<=l && r<=R){
    		int mid=(l+r)>>1;
    		if(better(c,mx[x],mid)) swap(c,mx[x]);
    		int tl=better(mx[x],c,l),tr=better(mx[x],c,r);
    		if(l==r || (tl && tr)) return;
    		if(tl) ins(ch[x][1],mid+1,r,L,R,c);
    		else ins(ch[x][0],l,mid,L,R,c);
    		return;
    	}
    	int mid=(l+r)>>1;
    	if(L<=mid) ins(ch[x][0],l,mid,L,R,c);
    	if(R>mid) ins(ch[x][1],mid+1,r,L,R,c);
    }
    int Max(int x,int l,int r,int c){
    	if(l==r) return mx[x];
    	int mid=(l+r)>>1,ret;
    	if(c<=mid) ret=Max(ch[x][0],l,mid,c);
    	else ret=Max(ch[x][1],mid+1,r,c);
    	if(better(mx[x],ret,c)) ret=mx[x];
    	return ret;
    }
    

    吉司机线段树

    线段树合并

    一般合并的都是权值线段树,大多与树结构相关
    只有两颗树共有的节点才会往下访问,所以很快的。

    int merge(int x,int y){
        if(!x || !y) return x+y;
        sum[x]+=y;
        ch[x][0]=merge(ch[x][0],ch[y][0]);
        ch[x][1]=merge(ch[x][1],ch[y][1]);
        return x;
    }
    

    树状数组

    一维树状数组

    主要用途有三种:

    1. 维护原序列,支持单点修改、区间求和( (sum(x)) 求得是 (sumlimits_{i=1}^x a_i) )
    int n;
    ll c[N];
    void add(int x,ll y) { while(x<=n) c[x]+=y,x+=x&(-x); }
    ll sum(int x){
    	ll ret=0;
    	while(x) ret+=c[x],x-=x&(-x);
    	return ret;
    }
    单点x+y:add(x,y)
    查询[l,r]的和:sum(r)-sum(l-1)
    
    1. 维护差分序列,支持区间修改、单点求值 (区间修改的时候注意要差分!(sum(x)) 求的是 (a_x) )
    函数同上
    区间[l,r]+y:add(l,y),add(r+1,-y)
    单点x求值:sum(x)
    
    1. 维护差分序列及差分序列 ( imes id),支持区间修改、区间求值
      设差分序列为 (d[i]=a[i]-a[i-1]),则 (a[i]=sumlimits_{j=1}^i d[j])
      (sumlimits_{i=1}^na[i]=sumlimits_{i=1}^nsumlimits_{j=1}^i d[i]=sumlimits_{i=1}^n d[i] imes(n-i+1))
      所以维护两个树状数组,分别表示 (d[i])(d[i] imes i)
    int n;
    ll c[N],ci[N];
    void add(int x,ll y) { 
    	ll id=x;
    	while(x<=n) c[x]+=y,ci[x]+=y*id,x+=x&(-x); 
    }
    ll sum(int x){
    	ll ret=0,id=x;
    	while(x) ret+=c[x]*(id+1)-ci[x],x-=x&(-x);
    	return ret;
    }
    区间[l,r]+y:add(l,y),add(r+1,-y)
    区间[l,r]求和:sum(r)-sum(l-1)
    

    其他用途:作为树套树中的外围

    二维树状数组

    主要用途有三种:

    1. 单点修改,区间查询
      树状数组套树状数组哈哈~
    int n,m;
    ll c[N][N];
    void add(int x,int y,int val){
    	for(int i=x;i<=n;i+=i&(-i))
    		for(int j=y;j<=m;j+=j&(-j))
    			c[i][j]+=val;
    }
    ll sum(int x,int y){
    	ll ret=0;
    	for(int i=x;i>0;i-=i&(-i))
    		for(int j=y;j>0;j-=j&(-j))
    			ret+=c[i][j];
    	return ret;
    }
    
    1. 区间修改,单点查询
      以前的博客
    2. 区间修改,区间查询
      以前的博客
      贴个代码吧
    int n,m;
    ll c[N][N],ci[N][N],cj[N][N],cij[N][N];
    void add(int x,int y,int val){
    	for(int i=x;i<=n;i+=i&(-i))
    		for(int j=y;j<=m;j+=j&(-j)){
    			c[i][j]+=val;
    			ci[i][j]+=1ll*x*val;
    			cj[i][j]+=1ll*y*val;
    			cij[i][j]+=1ll*x*y*val;
    		}
    }
    ll sum(int x,int y){
    	ll ret=0;
    	for(int i=x;i>0;i-=i&(-i))
    		for(int j=y;j>0;j-=j&(-j))
    			ret+=c[i][j]*(x+1)*(y+1)-ci[i][j]*(y+1)-cj[i][j]*(x+1)+cij[i][j];
    	return ret;
    }
    

    平衡树

    Treap

    推荐以前的博客
    维护一个有序序列,支持插入、删除,询问前驱后继、排名等
    就是在二叉搜索树基础上每个点加了随机值用来维护形态

    删除的细节有点多,注意随时判断 (root) 的变化!

    int cnt,root,rf,pa[N],ch[N][2],val[N],rnd[N],sz[N];
    void update(int x){ sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]]; }
    void rotate(int x,int ty){
    	int fa=pa[x],son=ch[x][ty^1],gp=pa[fa];
    	ch[fa][ty]=son; if(son) pa[son]=fa;
    	ch[x][ty^1]=fa; pa[fa]=x;
    	pa[x]=gp; ch[gp][fa==ch[gp][1]]=x;
    	if(fa==root) root=x;
    	update(fa); update(x);
    }
    int rr;
    int getrnd() { return rr=1ll*rr*4987657%39916801; }
    void ins(int x,int nd){
    	int f=val[x]<val[nd];
    	if(!ch[x][f]) ch[x][f]=nd,pa[nd]=x;
    	else ins(ch[x][f],nd);
    	update(x);
    	if(rnd[ch[x][f]]>rnd[x]) rotate(ch[x][f],f);
    }
    int find(int x,int k){
    	if(sz[ch[x][0]]>=k) return find(ch[x][0],k);
    	if(sz[ch[x][0]]==k-1) return x;
    	return find(ch[x][1],k-1-sz[ch[x][0]]);
    }
    int getrk(int x,int k){
    	if(!x) return 0;
    	if(val[x]<=k) return sz[ch[x][0]]+1+getrk(ch[x][1],k);
    	return getrk(ch[x][0],k);
    }
    int pre(int x,int k){
    	if(!x) return -INF;
    	if(val[x]<k) return max(pre(ch[x][1],k),val[x]);
    	return pre(ch[x][0],k); 
    }
    int sub(int x,int k){
    	if(!x) return INF;
    	if(val[x]>k) return min(sub(ch[x][0],k),val[x]);
    	return sub(ch[x][1],k);
    }
    void del(int x,int k){
    	if(val[x]==k){
    		if(ch[x][0] && ch[x][1]) {
    			int f=rnd[ch[x][0]]<rnd[ch[x][1]];
    			rotate(ch[x][f],f);
    			del(x,k);
    		}
    		else {
    			int f=ch[x][0]?0:1;
    			ch[pa[x]][x==ch[pa[x]][1]]=ch[x][f];
    			if(ch[x][f]) pa[ch[x][f]]=pa[x];
    			if(x==root) root=ch[x][f]; //don't forget!
    			x=pa[x];
    			while(x!=rf) update(x),x=pa[x];
    		}
    	}
    	else del(ch[x][k>val[x]],k);
    }
    

    FHQ-Treap

    非旋转 (treap) ,关键操作是 (merge) 将两棵树合并 ,(split) 将一棵树按权值或大小分为两棵树;(find) 查找排名为 (k) 的数也很重要,有时可以替掉按大小 (split)
    维护一个有序序列,支持插入、删除,询问前驱后继、排名等;区间操作也是没问题的!(所以能取代 (splay) 的很多操作)
    更重要的是支持可持久化

    int cnt,root,ch[N*2][2],sz[N*2],val[N*2],rnd[N*2];
    int rr;
    int getrnd() { return rr=1ll*rr*44987657%39916801; }
    void update(int x) { sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]]; }
    int merge(int x,int y){
    	if(!x || !y) return x+y;
    	if(rnd[x]>rnd[y]){
    		ch[x][1]=merge(ch[x][1],y);
    		update(x); return x;
    	}
    	else{
    		ch[y][0]=merge(x,ch[y][0]);
    		update(y); return y;
    	}
    }
    Pr split(int x,int k){ //by val
    	if(!x) return Pr(0,0);
    	Pr z;
    	if(val[x]<=k){
    		z=split(ch[x][1],k);
    		ch[x][1]=z.fi; update(x);
    		return Pr(x,z.se);
    	}
    	else{
    		z=split(ch[x][0],k);
    		ch[x][0]=z.se; update(x);
    		return Pr(z.fi,x);
    	}
    }
    int find(int x,int k){ //in tree x,rk k
    	if(sz[ch[x][0]]>=k) return find(ch[x][0],k);
    	if(sz[ch[x][0]]+1==k) return x;
    	return find(ch[x][1],k-1-sz[ch[x][0]]);
    }
    //-------------------------------------
    插入x:
    tmp=++cnt;
    ch[tmp][0]=ch[tmp][1]=0; sz[tmp]=1; val[tmp]=x; rnd[tmp]=getrnd();
    if(!root) root=tmp;
    else {
    	Pr w=split(root,x);
    	root=merge(merge(w.fi,tmp),w.se);
    }
    删除x:
    Pr w=split(root,x),z=split(w.fi,x-1);
    root=merge(z.fi,merge(merge(ch[z.se][0],ch[z.se][1]),w.se));
    查排名:
    Pr w=split(root,x-1); 
    printf("%d
    ",sz[w.fi]+1);
    root=merge(w.fi,w.se);
    前驱:
    Pr w=split(root,x-1);
    printf("%d
    ",val[find(w.fi,sz[w.fi])]);
    root=merge(w.fi,w.se);
    后继:
    Pr w=split(root,x);
    printf("%d
    ",val[find(w.se,1)]);
    root=merge(w.fi,w.se);
    

    Splay

    (treap) 的基础上可以把一个点旋转到指定位置,可以更方便的进行区间操作。
    (貌似大多数操作都能用 (FHQ-Treap) 代替……但还是很方便…)
    对区间 ([l,r]) 操作时,都是预先在序列前后各加一个数,然后把 第 (l-1) 个数转到 (rf) 之下,把第 (r+1) 个数转到 (root) 下,区间 ([l,r]) 就在 (r+1) 点的左子树。

    int root,rf,cnt,ch[N][2],pa[N],rev[N],val[N],sz[N];
    void pushdown(int x){
    	if(!rev[x]) return;
    	swap(ch[x][0],ch[x][1]);
    	rev[ch[x][0]]^=1; rev[ch[x][1]]^=1;
    	rev[x]=0;
    }
    void update(int x){ sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]]; }
    void rotate(int x,int ty){
    	int fa=pa[x],son=ch[x][ty^1],gp=pa[fa];
    	ch[fa][ty]=son; if(son) pa[son]=fa;
    	ch[x][ty^1]=fa; pa[fa]=x;
    	pa[x]=gp; ch[gp][fa==ch[gp][1]]=x;
    	if(fa==root) root=x;
    	update(fa); update(x);
    }
    void splay(int x,int t){
    	while(pa[x]!=t){
    		if(pa[pa[x]]==t) rotate(x,x==ch[pa[x]][1]);
    		else{
    			int fa=pa[x],gp=pa[fa];
    			int f=(fa==ch[gp][0]);
    			if(x==ch[fa][f]) rotate(x,f),rotate(x,!f);
    			else rotate(fa,!f),rotate(x,!f);
    		}
    	}
    }
    int find(int x,int k){
    	if(!x) return 0;
    	pushdown(x);
    	if(sz[ch[x][0]]>=k) return find(ch[x][0],k);
    	if(sz[ch[x][0]]+1==k) return x;
    	return find(ch[x][1],k-1-sz[ch[x][0]]);
    }
    void reverse(int l,int r){ //区间reverse
    	int x=find(root,l-1),y=find(root,r+1);
    	splay(x,rf); splay(y,root);
    	rev[ch[y][0]]^=1;
    }
    

    可并堆(左偏树)

    重点是每个点维护 (dis) 表示到最近的叶节点的距离
    左偏树是棵二叉树,满足左叉的 (dis) 大于右叉;由此可知一个非叶子结点的 (dis) 等于右子树 (dis+1)
    左偏树是个小根堆 (or) 大根堆,满足根节点是子树中的极值

    两个堆合并时,先判断根节点应来自哪棵树,然后另一棵树与这棵树的右子树合并;合并完后注意维护 (dis) 并适当交换左右子树使满足左偏条件。
    删除极值(根)时,把根的左右子树合并即可。
    删除任意节点时,找到这个点,把它的左右子树合并,再一路更新至根。

    有时候连通性用并查集维护

    int ch[N][2],dis[N],val[N];
    int merge(int x,int y){
    	if(!x || !y) return x+y;
    	if(val[x]>val[y] || (val[x]==val[y] && x>y)) swap(x,y);
    	ch[x][1]=merge(ch[x][1],y);
    	if(dis[ch[x][1]]>dis[ch[x][0]]) swap(ch[x][1],ch[x][0]);
    	dis[x]=dis[ch[x][1]]+1;
    	return x;
    }
    

    笛卡尔树

    对序列建立的类似大根堆 (or) 小根堆,中序遍历是原序列
    它可以告诉我们每个点是多大范围内的极值点
    建立完成后就是树上问题了
    经典问题是最大子矩形
    对于部分题目也是很妙的入手点

    (O(n)) 建树(栈维护最右侧的链)

    int n,root,ch[N][2],val[N];
    int st[N],top;
    void build(){
        top=0;
        for(int i=1;i<=n;i++){
            while(top && val[st[top]]>val[i]) top--;
            ch[i][0]=ch[st[top]][1]; ch[st[top]][1]=i'
            st[++top]=i;
        }
        root=ch[0][1];
    }
    

    作用:
    1.维护树链上的信息
    2.维护最大 (or) 最小生成树:注意要化边为点
    3.维护连通性
    4.维护边双连通分量:发现环后,把环上所有点指向另一个点,相当于缩成了一个点(可用并查集维护)
    5.维护原图(子树)信息:每个点新加一个附加值储存虚子树的贡献,每次虚实子树变化时(如 (access)(link))要更新这个值

    无删除时连通性用并查集判断比较快
    (find) 之后要加个 (splay),否则比较慢qwq

    int rt[N],ch[N][2],pa[N],rev[N],sum[N],val[N];
    void update(int x) { sum[x]=sum[ch[x][0]]^sum[ch[x][1]]^val[x]; }
    void pushdown(int x){
    	if(!rev[x]) return;
    	swap(ch[x][0],ch[x][1]);
    	rev[ch[x][0]]^=1; rev[ch[x][1]]^=1;
    	rev[x]=0;
    }
    int st[N],top;
    void push(int x){
    	top=0; st[++top]=x;
    	while(!rt[x]){
    		x=pa[x];
    		st[++top]=x;
    	}
    	while(top) pushdown(st[top--]);
    }
    void rotate(int x,int ty){
    	int son=ch[x][ty^1],fa=pa[x],gp=pa[fa];
    	ch[fa][ty]=son; if(son) pa[son]=fa;
    	ch[x][ty^1]=fa; pa[fa]=x;
    	pa[x]=gp;
    	if(rt[fa]) rt[fa]=0,rt[x]=1;
    	else ch[gp][fa==ch[gp][1]]=x;
    	update(fa); update(x);
    }
    void splay(int x){
    	push(x);
    	while(!rt[x]){
    		if(rt[pa[x]])
    			rotate(x,x==ch[pa[x]][1]);
    		else{
    			int fa=pa[x],gp=pa[fa];
    			int f=(fa==ch[gp][0]);
    			if(x==ch[fa][f]) rotate(x,f),rotate(x,!f);
    			else rotate(fa,!f),rotate(x,!f);
    		}
    	}
    }
    
    void access(int x){
    	int y=0;
    	while(x){
    		splay(x);
    		if(ch[x][1]) rt[ch[x][1]]=1; 
    		rt[y]=0; ch[x][1]=y;
    		update(x);
    		y=x; x=pa[x];
    	}
    }
    void makeroot(int x) { access(x); splay(x); rev[x]^=1; }
    int find(int x){
    	splay(x);
    	while(ch[x][0]) x=ch[x][0];
    	splay(x); return x;
    }
    void link(int x,int y) {
    	if(find(x)==find(y)) return;
    	makeroot(x); pa[x]=y;
    }
    void cut(int x,int y){
    	makeroot(x); access(y); splay(y);
    	if(ch[y][0]==x && !ch[x][1]){
    		rt[x]=1; pa[x]=0;
    		ch[y][0]=0;
    		update(y);
    	}
    }
    

    可持久化数据结构

    可持久化线段树(主席树)

    一般都是可持久化权值线段树
    单点修改成一个新版本没啥,记得每次新建点,未新建的部分指向上一版本的结点
    区间修改可以标记永久化,记得每次新建点
    注意空间开够

    可持久化trie

    一般都是01trie,操作和主席树没什么区别
    但用处很多,尤其是与二进制相关的,经典题求最大异或和

    可持久化treap

    (fhq-treap) 的基础上可持久化,(merge)(split) 是都新建节点比较保险,但实际由于 (merge) 基本都在 (split) 后进行,会被 (merge) 用到的结点已经是该版本新建的了,就不用再建了。

    int cnt,ch[N*20][2],val[N*20],rnd[N*20],rt[N],sz[N*20];
    void update(int x) { sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]]; }
    int merge(int x,int y){
    	if(!x || !y) return x+y;
    	if(rnd[x]>rnd[y]){
    		ch[x][1]=merge(ch[x][1],y);
    		update(x); return x;
    	}
    	else{
    		ch[y][0]=merge(x,ch[y][0]);
    		update(y); return y;
    	}
    }
    Pr split(int x,int k){//split by size
    	if(!x) return Pr(0,0);
    	int z=++cnt;
    	val[z]=val[x]; rnd[z]=rnd[x];
    	if(sz[ch[x][0]]>=k){
    		Pr w=split(ch[x][0],k);
    		ch[z][1]=ch[x][1]; ch[z][0]=w.se;
    		update(z); return Pr(w.fi,z);
    	}
    	else{
    		Pr w=split(ch[x][1],k-sz[ch[x][0]]-1);
    		ch[z][0]=ch[x][0]; ch[z][1]=w.fi;
    		update(z); return Pr(z,w.se);
    	}
    }
    

    可持久化可并堆

    可持久化并查集


    树套树

    二维数点

    静态离线:扫描线,一维排序、另一位树状数组维护
    静态在线:主席树
    动态离线:CDQ分治
    动态在线:范围小的可以二维树状数组,范围大的树状数组套主席树

    三维偏序(树状数组套treap)

    因为三维偏序没有区间内第 (k) 大类似的操作,所以用树状数组差分就可以了

    int cnt,root[N],ch[N*20][2],sz[N*20],val[N*20],rnd[N*20];
    void update(int x) { sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]]; }
    int merge(int x,int y){
    	if(!x || !y) return x+y;
    	if(rnd[x]>rnd[y]){
    		ch[x][1]=merge(ch[x][1],y);
    		update(x); return x;
    	}
    	else{
    		ch[y][0]=merge(x,ch[y][0]);
    		update(y); return y;
    	}
    }
    Pr split(int x,int k){
    	if(!x) return Pr(0,0);
    	Pr z;
    	if(val[x]<=k) {
    		z=split(ch[x][1],k);
    		ch[x][1]=z.fi; update(x);
    		return Pr(x,z.se);
    	}
    	else{
    		z=split(ch[x][0],k);
    		ch[x][0]=z.se; update(x);
    		return Pr(z.fi,x);
    	}
    }
    int sml(int id,int k){
    	Pr z=split(root[id],k);
    	int ret=sz[z.fi];
    	root[id]=merge(z.fi,z.se);
    	return ret;
    }
    
    int n,k;
    void add(int x,int y){
    	while(x<=k){
    		int tmp=++cnt;
    		val[tmp]=y; sz[tmp]=1; rnd[tmp]=getrnd();
    		Pr z=split(root[x],y);
    		root[x]=merge(z.fi,merge(tmp,z.se));
    		
    		x+=x&(-x);
    	}
    }
    int sum(int x,int y){
    	int ret=0;
    	while(x){
    		ret+=sml(x,y);
    		x-=x&(-x);
    	}
    	return ret;
    }
    
    

    线段树套线段树

    有可能空间爆炸,那样就需要牺牲时间换成 (KD-Tree)
    当然也可以离线

    树状数组套主席树(线段树)

    相当于可修改历史版本的主席树
    用树状数组动态维护前缀。

    线段树套平衡树

    不维护减的话只能用线段树了
    模板题二逼平衡树,写的是线段树套 (FHQtreap)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    #define fi first
    #define se second
    #define INF 1000000000
    
    using namespace std;
    
    int read(){
    	int x=0,f=1;
    	char ch=getchar();
    	while(!isdigit(ch) && ch!='-') ch=getchar();
    	if(ch=='-') f=-1,ch=getchar();
    	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    	return x*f;
    }
    
    const int N = 50005;
    typedef pair<int,int> Pr;
    
    int rr;
    int getrnd() { return rr=1ll*rr*rr%4987657+1ll*rr*961275%39916801; } //rr=!!!
    
    int n,m,tot;
    int root[N*2],ch[N*20][2],val[N*20],rnd[N*20],sz[N*20];
    void update(int x) { sz[x]=1+sz[ch[x][0]]+sz[ch[x][1]]; }
    int merge(int x,int y){
    	if(!x || !y) return x+y;
    	if(rnd[x]>rnd[y]){
    		ch[x][1]=merge(ch[x][1],y);
    		update(x); return x;
    	}
    	else{
    		ch[y][0]=merge(x,ch[y][0]);
    		update(y); return y;
    	}
    }
    Pr split(int x,int k){ //by val
    	if(!x) return Pr(0,0);
    	Pr z;
    	if(val[x]<=k){
    		z=split(ch[x][1],k);
    		ch[x][1]=z.fi; update(x);
    		return Pr(x,z.se);
    	}
    	else{
    		z=split(ch[x][0],k);
    		ch[x][0]=z.se; update(x);
    		return Pr(z.fi,x);
    	}
    }
    int find(int x,int k){
    	if(!x) return 0;
    	if(sz[ch[x][0]]>=k) return find(ch[x][0],k);
    	if(sz[ch[x][0]]+1==k) return x;
    	return find(ch[x][1],k-sz[ch[x][0]]-1);
    }
    int pre(int id,int k){
    	Pr z=split(root[id],k-1);
    	int x=find(z.fi,sz[z.fi]);
    	root[id]=merge(z.fi,z.se); 
    	return x?val[x]:-INF; //x?
    }
    int sub(int id,int k){
    	Pr z=split(root[id],k);
    	int x=find(z.se,1);
    	root[id]=merge(z.fi,z.se);
    	return x?val[x]:INF; //x?
    }
    int pos[N],bb[N];
    void modify(int id,int x,int y){ //from x->y
    	Pr w=split(root[id],x),t=split(w.fi,x-1);
    	int tmp=t.se; 
    	root[id]=merge(merge(t.fi,merge(ch[tmp][0],ch[tmp][1])),w.se);
    	val[tmp]=y; sz[tmp]=1; ch[tmp][0]=ch[tmp][1]=0;
    	w=split(root[id],y-1);
    	root[id]=merge(merge(w.fi,tmp),w.se);
    }
    int num(int x,int k){
    	if(!x) return 0;
    	if(val[x]<=k) return sz[ch[x][0]]+1+num(ch[x][1],k);
    	return num(ch[x][0],k);
    }
    
    int cnt,rt,son[N*2][2];
    void build(int x,int l,int r){
    	root[x]=0;
    	for(int i=l;i<=r;i++) bb[i-l+1]=pos[i];
    	sort(bb+1,bb+1+r-l+1);
    	for(int i=1;i<=r-l+1;i++){
    		int tmp=++tot; 
    		val[tmp]=bb[i]; sz[tmp]=1; rnd[tmp]=getrnd();
    		root[x]=merge(root[x],tmp);
    	}
    	
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	build(son[x][0]=++cnt,l,mid);
    	build(son[x][1]=++cnt,mid+1,r);
    }
    int Pre(int x,int l,int r,int L,int R,int c){
    	if(L<=l && r<=R) return pre(x,c);
    	int mid=(l+r)>>1,ret=-INF;
    	if(L<=mid) ret=max(ret,Pre(son[x][0],l,mid,L,R,c));
    	if(R>mid) ret=max(ret,Pre(son[x][1],mid+1,r,L,R,c));
    	return ret;
    }
    int Sub(int x,int l,int r,int L,int R,int c){
    	if(L<=l && r<=R) return sub(x,c);
    	int mid=(l+r)>>1,ret=INF;
    	if(L<=mid) ret=min(ret,Sub(son[x][0],l,mid,L,R,c));
    	if(R>mid) ret=min(ret,Sub(son[x][1],mid+1,r,L,R,c));
    	return ret;
    }
    void Modify(int x,int l,int r,int c,int fr,int to){
    	modify(x,fr,to);
    	if(l==r) return;
    	int mid=(l+r)>>1;
    	if(c<=mid) Modify(son[x][0],l,mid,c,fr,to);
    	else Modify(son[x][1],mid+1,r,c,fr,to);
    }
    int rk(int x,int l,int r,int L,int R,int c){
    	if(L<=l && r<=R) return num(root[x],c-1);
    	int mid=(l+r)>>1,ret=0;
    	if(L<=mid) ret+=rk(son[x][0],l,mid,L,R,c);
    	if(R>mid) ret+=rk(son[x][1],mid+1,r,L,R,c);
    	return ret;
    }
    
    int kth(int L,int R,int k){
    	int l=-INF,r=INF,mid;
    	while(l<r){
    		mid=(l+r+1)>>1;
    		if(rk(rt,1,n,L,R,mid)<k) l=mid;
    		else r=mid-1;
    	}
    	return l;
    }
    
    int main()
    {
    	rr=1;
    	n=read(); m=read();
    	for(int i=1;i<=n;i++) pos[i]=read();
    	build(rt=++cnt,1,n);
    	int ty,l,r,x;
    	while(m--){
    		ty=read(); 
    		if(ty==3){
    			l=read(); x=read();
    			Modify(rt,1,n,l,pos[l],x);
    			pos[l]=x;
    		}
    		else{
    			l=read(); r=read(); x=read();
    			if(ty==1) printf("%d
    ",rk(rt,1,n,l,r,x)+1);
    			else if(ty==2) printf("%d
    ",kth(l,r,x));
    			else if(ty==4) printf("%d
    ",Pre(rt,1,n,l,r,x));
    			else printf("%d
    ",Sub(rt,1,n,l,r,x));
    		}
    	}
    	
    	return 0;
    }
    

    KD-Tree

    (反正是解决高维问题,就算在这里吧)


    离线做法

    CDQ分治

    整体二分

    线段树分治


    分块


    莫队

  • 相关阅读:
    大数加法、乘法实现的简单版本
    hdu 4027 Can you answer these queries?
    zoj 1610 Count the Colors
    2018 徐州赛区网赛 G. Trace
    1495 中国好区间 尺取法
    LA 3938 动态最大连续区间 线段树
    51nod 1275 连续子段的差异
    caioj 1172 poj 2823 单调队列过渡题
    数据结构和算法题
    一个通用分页类
  • 原文地址:https://www.cnblogs.com/lindalee/p/13429545.html
Copyright © 2011-2022 走看看