zoukankan      html  css  js  c++  java
  • 主席树总结(经典区间第k小问题)(主席树,线段树)

    接着上一篇总结——可持久化线段树来整理吧。点击进入
    这两种数据结构确实有异曲同工之妙。结构是很相似的,但维护的主要内容并不相同,主席树的离散化、前缀和等思想也要更难理解一些。

    闲话

    话说刚学习主席树的时候百度了一下,看到了“主席树”这一名字的由来——

    线段树竟然是被一个黄嘉泰的大佬因不会划分树来代替的,,,,,因缩写是HJT取名为主席树= =!orz

    我的内心瞬间感到了不安。。。。。。(看我的名字,就在右边)
    能跟神犇有相同的缩写是何等荣幸!看来这个主席树我得好好学了。
    那么接下来进入正题。

    主席树

    直接从最经典的应用——区间第(k)小问题开始吧。ZSY巨佬对这一问题的思路挺清晰的,本蒟蒻在这里也参考一下。%ZSY%请点这里

    1. 静态区间第k小问题

    洛谷题目传送门
    即给出一个序列,每次询问求给定区间([l,r])内第(k)小的值

    思路分析

    先不考虑每一个区间的情况,从最简单的查询整个区间([1,r])的情况开始。
    对数据离散化后,用一个线段树来维护,每个节点维护对应离散化后值区间的数的总个数(size)。自上至下进行询问操作时,判断当前点左子树的(size)与要查询的排名(k)的大小关系。如果小于等于,就到左子树中找,(k)不变。否则到右子树中找排名(k-size)的值。这与平衡树(Splay,Treap)等查询给定排名数的方法是基本一样的。
    那对于所有可能的区间,又该怎样维护呢?我们其实只要(N)个线段树就好了,第(i)个线段树维护([1,i])的情况。这里我们利用了前缀和的性质。查询([l,r])就等于查询([1,r])减去([1,l-1])的对应的(size)没错吧。因为线段树是完全二叉树,具有结构稳定的性质,所以(N)个线段树长得是一样的,对应区间相减是可行的。
    然而暴力开(N)个空间保准炸掉。这时候我们回头看看可持久化线段树是怎么做的。没错,从([1,i-1])([1,i])也只变了一个值!于是同样只要新开(log)个节点,保存(root_i)(i)对应叶子节点的路径就OK了。
    拿洛谷题目里的样例来几张图吧,好理解些。
    首先是离散化后的序列。

    我们一开始要建一棵空线段树,除了有个结构,所有的(size)均为(0)。然后一个一个的添加线段树。加入([1,1])的线段树后会是这样:

    再加入([1,2])

    后面的手推一下吧。。。。。。
    至此,(N)棵树就建好了,并且只用了(N log N)的空间。
    查询的时候存两个点,一开始为(root_r)(root_{l-1}),两个(size)的差与(k)来比大小,两个点要同时往左/右跳。
    更多细节就看代码吧。

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define R register int
    const int N=200009,M=5000009;
    int P,a[N],b[N],rt[N],lc[M],rc[M],s[M];
    #define G c=getchar()
    inline void in(R&z)
    {
    	register bool f=0;
    	register char G;
    	while(c<'-')G;
    	if(c=='-')f=1,G;
    	z=c&15;G;
    	while(c>'-')z=(z<<3)+(z<<1)+(c&15),G;
    	if(f)z=-z;
    }//快读
    void build(R&t,R l,R r)
    {
    	t=++P;
    	if(l!=r)
    	{
    		R m=(l+r)>>1;
    		build(lc[t],l,m);
    		build(rc[t],m+1,r);
    	}
    }//线段树操作,建一个空线段树
    inline void insert(R*t,R u,R l,R r,R v)
    {
    	while(l!=r)
    	{
    		s[*t=++P]=s[u]+1;//注意这里要+1
    		R m=(l+r)>>1;
    		if(v<=m)r=m,rc[*t]=rc[u],t=&lc[*t],u=lc[u];
    		else  l=m+1,lc[*t]=lc[u],t=&rc[*t],u=rc[u];
    	}
    	s[*t=++P]=s[u]+1;
    }//插入操作在可持久化线段树总结里面有更详细的介绍
    inline int ask(R t,R u,R l,R r,R k)
    {
    	while(l!=r)
    	{
    		R m=(l+r)>>1,v=s[lc[u]]-s[lc[t]];//作差
    		if(k<=v)r=m,t=lc[t],u=lc[u];//两个点一起跳
    		else  l=m+1,t=rc[t],u=rc[u],k-=v;
    	}
    	return b[l];
    }
    int main()
    {
    	R n,m,i,l,r,sz;
    	in(n);in(m);
    	for(i=1;i<=n;++i)
    		in(a[i]),b[i]=a[i];
    	sort(b+1,b+n+1);
    	sz=unique(b+1,b+n+1)-b-1;//离散化,排序去重
    	build(rt[0],1,sz);
    	for(i=1;i<=n;++i)
    		insert(&rt[i],rt[i-1],1,sz,lower_bound(b+1,b+sz+1,a[i])-b);//直接用STL的二分找对应值了
    	while(m--)
    	{
    		in(l);in(r);in(i);
    		printf("%d
    ",ask(rt[l-1],rt[r],1,sz,i));
    	}
    	return 0;
    }
    

    2. 动态区间第k小问题

    洛谷题目传送门
    就是比上面那题多了个修改。。。。。。
    改为树状数组维护前缀和,使修改复杂度降低。
    详细题解在此

    3.树上路径第k小问题

    洛谷题目传送门
    只维护树根节点(随便设)到每个节点的前缀和,查询时(size[root,u]+size[root,v]-size[root,lca(u,v)]-size[root,father(lca[u,v))])即为(u->v)路径的(size)
    于是用倍增求LCA。
    详细题解在此

  • 相关阅读:
    初探XML
    Hibernate 由实体类与配置文件的配置关系生成数据库中的表
    利用JSP中的过滤器解决中文乱码问题
    关于iBatis配置xml文件时出现中文注释出错的一个问题(很坑爹.)
    Myeclipse中xml文件里自动提示消失解决办法
    iBatis的基本配置+CRUD操作
    Myeclipse下配置svn
    Hibernate与iBastis 比较(转载)
    本人了解的分页查询
    Hibernate五大核心接口简介
  • 原文地址:https://www.cnblogs.com/flashhu/p/8301774.html
Copyright © 2011-2022 走看看