区间第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\n",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\n",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;
}
posted @ 2020-05-19 16:30  魑吻丶殇之玖梦  阅读(325)  评论(0编辑  收藏  举报