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分治

    整体二分

    线段树分治


    分块


    莫队

  • 相关阅读:
    图像 resize 代码:保留 aspect ratio(长宽比)
    Pytorch lr_scheduler 中的 last_epoch 用法
    torch.optim用法(参数组的设置)
    课程式学习(Curriculum Learning)
    扇贝单词本-隐藏中文释义 tampermonkey
    电话号码正向标记认证网站申请地址
    考研英语做题计时器网页版(每隔3分钟播放声音,提醒计时)
    mac关闭自动更新后还在每天提醒进行安装更新
    mac 自动生成自签证书脚本
    Ditto
  • 原文地址:https://www.cnblogs.com/lindalee/p/13429545.html
Copyright © 2011-2022 走看看