zoukankan      html  css  js  c++  java
  • 区间第k小数问题

    静态区间第k小数

    原题链接

    算法概述

    我们在“值域”上建立线段树。每个节点维护一段值域区间[L,R],并记录序列中数值落在这段值域区间[L,R]内的点有多少个,记为cnt。

    先不考虑下标区间[l,r]的限制。对于询问整个序列A1~An中的第k小数,我们执行线段树的查询操作,对于每个线段树上的节点,只需比较k与左儿子(其值域区间为[L,mid])的cnt,若k<=l.cnt,那么就可以递归查询左子树中的第k小数,否则递归查询右子树中的第(k-l.cnt)小数。

    当然,鉴于Ai的规模,我们建立要建立权值线段树就需要先离散化。

    考虑下标区间[l,r]。

    显然,只建立一棵线段树是不能够支持我们的操作的。于是我们便需要建立主席树,考虑将序列A1,A2,A3,……,An依次插入主席树中,那么每一棵主席树的根节点root[i]所对应的其所维护的序列就是A1~Ai,以root[i]为根节点的主席树(值域区间[L,R])就保存了序列A的前i个数中落在[L,R]内的有多少个。

    容易发现一条很重要的性质:由于每棵主席树中的所有节点所代表的值域区间都是一一对应的,这就意味着: 我们取两个序列A中的下标l,r。 以root[r]为根的主席树中保存了序列A的前r个数中落在[L,R]内的有多少个, 以root[l]为根的主席树中保存了序列A的前l个数中落在[L,R]内的有多少个。 所以root[r].cnt-root[l].cnt就是序列A下标区间[l+1,r]中落在值域区间[L,R]内的数有多少个。这样我们就把下标区间的问题解决了。

    对于每次询问l,r,k。 我们同时遍历以root[r]为根和以root[l-1]为根的两棵主席树,在每一个点上,计算出两者左子树的cnt之差,然后与k作比较,若k较小,则往左递归,若k较大,则往右递归。就是我们刚才讲过的过程了。

    于是这个问题就解决了。 当然该问题还有整体二分、归并树、线段树套平衡树等做法,我们不予讨论。、

    整个算法时间复杂度O((n+m)logn),空间复杂度O(nlogn)。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N=2e5+10;
    
    struct Node{
    	int l,r;
    	int sum;
    	#define l(p) tree[p].l
    	#define r(p) tree[p].r
    	#define sum(p) tree[p].sum
    }tree[N*22];int idx;
    int root[N];
    int a[N],b[N];
    int n,m,t;
    
    int build(int l,int r)
    {
    	int p=++idx;
    	if(l==r)return p;
    	int mid=l+r>>1;
    	l(p)=build(l,mid);
    	r(p)=build(mid+1,r);
    	return p;
    } 
    
    int insert(int now,int l,int r,int x,int val)
    {
    	int p=++idx;
    	tree[p]=tree[now];
    	if(l==r){sum(p)+=val;return p;}
    	int mid=l+r>>1;
    	if(x<=mid)l(p)=insert(l(now),l,mid,x,val);
    	else r(p)=insert(r(now),mid+1,r,x,val);
    	sum(p)=sum(l(p))+sum(r(p));
    	return p;
    }
    
    int query(int u,int v,int l,int r,int k)
    {
    	if(l==r)return l;
    	int lcnt=sum(l(v))-sum(l(u));
    	int mid=l+r>>1;
    	if(k<=lcnt)return query(l(u),l(v),l,mid,k);
    	else return query(r(u),r(v),mid+1,r,k-lcnt);
    }
    
    int find(int x)
    {
    	return lower_bound(b+1,b+t+1,x)-b;
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[i]=a[i];
    	
    	sort(b+1,b+n+1);
    	t=unique(b+1,b+n+1)-(b+1);
    	
    	root[0]=build(1,t);
    	for(int i=1;i<=n;i++)root[i]=insert(root[i-1],1,t,find(a[i]),1);
    	
    	while(m--)
    	{
    		int l,r,k;
    		scanf("%d%d%d",&l,&r,&k);
    		int ans=query(root[l-1],root[r],1,t,k);
    		printf("%d
    ",b[ans]);
    	} 
    	
    	return 0;
    }
    

    动态区间第k小数

    原题链接

    算法概述

    基于我们刚才主席树的做法,让以root[i]为根的主席树维护序列A1~Ai的信息,则当我们要将Ax作出修改时,其修改结果会影响到以root[x]、root[x+1]、……、root[n]为根的共n-x+1棵主席树,如果以朴素的做法,每次都把这些主席树全部维护的话,时间复杂度是O(nm)的,显然不行。

    但是这也为我们提供了思路。这与前缀和十分相似,当我们修改中间某个点时,自然就会对其后所有前缀和产生影响。而我们的主席树并不善于维护前缀和,所以我们应该交给擅长维护前缀和的树状数组去做。

    对于每棵以root[i]为根的子树,我们让其维护序列A[i-lowbit(i)+1~i]的信息。

    这样对于每次单点修改,它最多只会影响到logn个root,也就是只会影响logn棵主席树,故我们只需在每次修改时,凭借树状数组的单点修改操作处理出需要维护哪logn棵主席树,然后同时进行维护即可。

    对于查询操作也是同样的,我们通过树状数组的区间查询操作处理出需要查询哪logn棵主席树,然后同时遍历。

    树状数组的查询操作查询的就是前缀和,对于询问的区间l,r,我们要分别处理两组logn棵主席树,第一组是l-1的前缀和,第二组是r的前缀和。

    然后在遍历过程中需要与k进行比较然后判断递归左子树还是右子树的那一步,我们只需将第二组主席树的左儿子的cnt求和,减去第一组主席树的左儿子的cnt之和,再将结果与k进行比较即可。

    因为这道题需要离散化,所以我们得离线做。 

    整个算法时间复杂度O(mlog2n),空间复杂度O(nlog2n)。

    参考代码

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    using namespace std;
    const int N=1e5+10;
    
    struct Ques{
    	int tp;
    	int l,r,k;
    	int pos,val;
    }q[N];
    
    struct Node{
    	int l,r;
    	int cnt;
    	#define l(p) tree[p].l
    	#define r(p) tree[p].r
    	#define cnt(p) tree[p].cnt
    }tree[N*20];int idx;
    int root[N];
    int a[N],b[N<<1];
    int tmp[2][20],cnt[2];
    int n,m,t;
    
    int lowbit(int x)
    {
    	return x&-x;
    }
    
    void modify(int &p,int l,int r,int pos,int val)
    {
    	if(!p)p=++idx;
    	if(l==r){cnt(p)+=val;return;}
    	int mid=l+r>>1;
    	if(pos<=mid)modify(l(p),l,mid,pos,val);
    	else modify(r(p),mid+1,r,pos,val);
    	cnt(p)=cnt(l(p))+cnt(r(p));
    }
    
    void pre_modify(int x,int val)
    {
    	int k=lower_bound(b+1,b+t+1,a[x])-b;
    	for(int i=x;i<=n;i+=lowbit(i))modify(root[i],1,t,k,val);
    }
    
    int query(int l,int r,int k)
    {
    	if(l==r)return l;
    	int sum=0;
    	for(int i=1;i<=cnt[0];i++)sum+=cnt(l(tmp[0][i]));
    	for(int i=1;i<=cnt[1];i++)sum-=cnt(l(tmp[1][i]));
    	int mid=l+r>>1;
    	if(k<=sum)
    	{
    		for(int i=1;i<=cnt[0];i++)tmp[0][i]=l(tmp[0][i]);
    		for(int i=1;i<=cnt[1];i++)tmp[1][i]=l(tmp[1][i]);
    		return query(l,mid,k);
    	} 
    	else
    	{
    		for(int i=1;i<=cnt[0];i++)tmp[0][i]=r(tmp[0][i]);
    		for(int i=1;i<=cnt[1];i++)tmp[1][i]=r(tmp[1][i]);
    		return query(mid+1,r,k-sum); 
    	}
    }
    
    int pre_query(int l,int r,int k)
    {
    	memset(tmp,0,sizeof tmp);
    	cnt[0]=cnt[1]=0;
    	for(int i=r;i;i-=lowbit(i))tmp[0][++cnt[0]]=root[i];
    	for(int i=l-1;i;i-=lowbit(i))tmp[1][++cnt[1]]=root[i];
    	return query(1,t,k);
    }
    
    int main()
    {
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++)scanf("%d",&a[i]),b[++t]=a[i];
    	for(int i=1;i<=m;i++)
    	{
    		char tp[2];scanf("%s",tp);
    		if(tp[0]=='Q')
    		{
    			int l,r,k;scanf("%d%d%d",&l,&r,&k);
    			q[i].tp=1,q[i].l=l,q[i].r=r,q[i].k=k;
    		}
    		else
    		{
    			int x,val;scanf("%d%d",&x,&val);
    			q[i].tp=2,q[i].pos=x,q[i].val=val;b[++t]=val;
    		}
    	}
    	
    	sort(b+1,b+t+1);
    	t=unique(b+1,b+t+1)-(b+1);
    
    	for(int i=1;i<=n;i++)pre_modify(i,1);
    	
    	for(int i=1;i<=m;i++)
    	{
    		if(q[i].tp==1)
    		{
    			int l=q[i].l,r=q[i].r,k=q[i].k;
    			int ans=pre_query(l,r,k);
    			printf("%d
    ",b[ans]);
    		}
    		else
    		{
    			int x=q[i].pos,val=q[i].val;
    			pre_modify(x,-1);
    			a[x]=val;
    			pre_modify(x,1);
    		}
    	}	
    	return 0;
    }
  • 相关阅读:
    java数据结构——哈希表(HashTable)
    java数据结构——红黑树(R-B Tree)
    java数据结构——二叉树(BinaryTree)
    java数据结构——递归(Recursion)例题持续更新中
    电路布线
    Cordova 入门
    mysql 分组加行号
    数据库表添加行号
    java jsp自定义标签
    java web Listener的简单使用案例
  • 原文地址:https://www.cnblogs.com/ninedream/p/12917780.html
Copyright © 2011-2022 走看看