ZOJ 2112 Dynamic Rankings [树状数组套主席树 || CDQ分治&整体二分]

题意:给出长度为n的数组,有两种操作

1、询问区间【L,R】第k大的数的权值

2、将第i个数修改为t

题解:

①第一种方法是树状数组套主席树,要建立关于原数组的主席树和关于修改的树状数组套主席树,两个主席树都以权值建树,由于某个点改变所以影响到很多区间,而树状数组的每一个点都可以代表一个区间,所以我们通过树状数组logn查找相应需要修改的所有的区间,再通过logn来修改相应区间上点的权值,若每次修改都在树状数组上面新建一颗线段树则会m*n*logn爆空间,所以我们通过主席树动态加点,所以询问操作的总共需要保存的空间为m*logn,然后关于原数组的主席树空间为n*logn,所以总的

空间复杂度为(n+m)*logn,然后由于新建一个关于原数组的主席树的时间复杂度为n*logn,修改操作总的复杂度为m*logn*logn所以总的时间复杂度为m*logn*logn。

树状数组套主席树AC代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<map>
using namespace std;
#define N 50005
int n,m;
int a[N],la[N];
int b[N*2],top;
int q[N][4];
map<int,int>mp;
int tree[N*40],lchild[N*40],rchild[N*40];
int root[N],tot;
int c[N],use[N];
void update(int last,int cur,int L,int R,int x,int k)
{
	tree[cur]=tree[last]+k;
	lchild[cur]=lchild[last];
	rchild[cur]=rchild[last];
	if(L==R)return ;
	int mid=L+R>>1;
	if(x<=mid)update(lchild[last],lchild[cur]=++tot,L,mid,x,k);
	else update(rchild[last],rchild[cur]=++tot,mid+1,R,x,k);
}
int lowbit(int i)
{
	return i&(-i);
}
void add(int i,int v,int k)
{
	while(i<=n)
	{
		int last=c[i];
		update(last,c[i]=++tot,1,top,mp[v],k);
		i+=lowbit(i);
	}
}
int sum(int i)
{
	int ans=0;
	while(i)
	{
		ans+=tree[lchild[use[i]]];
		i-=lowbit(i);
	}
	return ans;
}
int query(int l,int r,int k)
{
	for(int i=l-1;i;i-=lowbit(i))use[i]=c[i];
	for(int i=r;i;i-=lowbit(i))use[i]=c[i];
	int L=1,R=top;
	int last=root[l-1],cur=root[r];
	while(1)
	{
		if(L==R)return b[L-1];
		int mid=L+R>>1;
		int lsum=tree[lchild[cur]]-tree[lchild[last]]+sum(r)-sum(l-1);
		if(lsum>=k)
		{
			R=mid;
			for(int i=l-1;i;i-=lowbit(i))use[i]=lchild[use[i]];
			for(int i=r;i;i-=lowbit(i))use[i]=lchild[use[i]];
			last=lchild[last];
			cur=lchild[cur];
		}
		else
		{
			k-=lsum;
			L=mid+1;
			for(int i=l-1;i;i-=lowbit(i))use[i]=rchild[use[i]];
			for(int i=r;i;i-=lowbit(i))use[i]=rchild[use[i]];
			last=rchild[last];
			cur=rchild[cur];
		}
	}
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		tot=top=0;
		mp.clear();
		memset(c,0,sizeof(c));
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
		{
			scanf("%d",&a[i]);
			b[top++]=a[i];
			la[i]=a[i];
		}
		for(int i=0;i<m;i++)
		{
			char op[2];
			scanf("%s",op);
			if(op[0]=='Q')
			{
				int l,r,k;
				scanf("%d%d%d",&l,&r,&k);
				q[i][0]=0;
				q[i][1]=l;
				q[i][2]=r;
				q[i][3]=k;
			}
			else
			{
				int pos,w;
				scanf("%d%d",&pos,&w);
				q[i][0]=1;
				q[i][1]=pos;
				q[i][2]=w;
				b[top++]=w;
			}
		}
		sort(b,b+top);
		top=unique(b,b+top)-b;
		for(int i=0;i<top;i++)
			mp[b[i]]=i+1;
		for(int i=1;i<=n;i++)
			update(root[i-1],root[i]=++tot,1,top,mp[a[i]],1);
		for(int i=0;i<m;i++)
		{
			if(q[i][0])
			{
				add(q[i][1],la[q[i][1]],-1);
				add(q[i][1],q[i][2],1);
				la[q[i][1]]=q[i][2];
			}
			else printf("%d\n",query(q[i][1],q[i][2],q[i][3]));
		}
	}
}

②第二种方法是CDQ分治中的整体二分

每次关于结果,也就是第k个权值为多少进行二分,通过树状数组维护每一次二分时权值小于x的点的个数,将询问分为两种,权值小于x的总点数小于等于k,第二种是总数大于k,然后通过归并排序,分为权值满足【L,mid】的部分和【mid+1,R】的部分继续递归下去。

由于CDQ分治总共logn次递归,每次递归查询和修改的总复杂度为n*logn所以总的时间复杂度为n*logn*logn,空间复杂度为nlogn。


整体二分AC代码:

#include<stdio.h>
#include<algorithm>
#include<map>
#include<string.h>
#define N 50005
using namespace std;
struct node
{
	int op,l,pos,k;
	int t;
	int add;
	int id;
}a[N*2],newa[N*2];
int t,top,tot;
int b[N*2];
int pos[N];
int c[N];
int cum[N],mark[N],ANS[N];
map<int,int>mp;
int lowbit(int i)
{
	return i&(-i);
}
void change(int i,int k)
{
	while(i<=top+1)
	{
		c[i]+=k;
		i+=lowbit(i);
	}
}
int sum(int i)
{
	int ans=0;
	while(i)
	{
		ans+=c[i];
		i-=lowbit(i);
	}
	return ans;
}
void solve(int posl,int posr,int l,int r)
{
	if(l==r)
	{
		for(int i=posl;i<=posr;i++)
			if(a[i].op==3)
				ANS[a[i].id]=l;
		return ;
	}
	int mid=(l+r)/2;
	for(int i=posl;i<=posr;i++)
	{
		if(a[i].op==1&&a[i].add<=mid)
			change(a[i].pos,1);
		if(a[i].op==2&&a[i].add<=mid)
			change(a[i].pos,-1);
		if(a[i].op==3)
		{
			mark[a[i].id]=sum(a[i].pos)-sum(a[i].l-1);
			cum[a[i].id]+=mark[a[i].id];
		}
	}
	for(int i=posl;i<=posr;i++)
	{
		if(a[i].op==1&&a[i].add<=mid)
			change(a[i].pos,-1);
		if(a[i].op==2&&a[i].add<=mid)
			change(a[i].pos,1);
	}
	int l1=posl;
	for(int i=posl;i<=posr;i++)
	{
		if((a[i].op==1||a[i].op==2)&&a[i].add<=mid)
			newa[l1++]=a[i];
		else if(a[i].op==3&&cum[a[i].id]>=a[i].k)
			newa[l1++]=a[i];
	}
	int l2=l1;
	for(int i=posl;i<=posr;i++)
	{
		if((a[i].op==1||a[i].op==2)&&a[i].add>mid)
			newa[l2++]=a[i];
		else if(a[i].op==3&&cum[a[i].id]<a[i].k)
		{
			mark[a[i].id]=0;
			newa[l2++]=a[i];
		}
	}
	for(int i=posl;i<=posr;i++)
	{
		a[i]=newa[i];
		if(a[i].op==3)
			cum[a[i].id]-=mark[a[i].id];
	}
	solve(posl,l1-1,l,mid);
	solve(l1,posr,mid+1,r);
}
void init()
{
	tot=top=t=0;
	memset(ANS,-1,sizeof(ANS));
	memset(cum,0,sizeof(cum));
}
int main()
{
	int T;
	scanf("%d",&T);
	while(T--)
	{
		init();
		mp.clear();
		int n,m;
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;i++)
		{
			int k;
			scanf("%d",&k);
			a[++tot].op=1;
			a[tot].pos=i;
			a[tot].t=++t;
			a[tot].add=k;
			b[top++]=k;
			pos[i]=k;
		}
		for(int i=1;i<=m;i++)
		{
			char op[2];
			scanf("%s",op);
			if(op[0]=='Q')
			{
				int l,r,k;
				scanf("%d%d%d",&l,&r,&k);
				a[++tot].op=3;
				a[tot].l=l;
				a[tot].pos=r;
				a[tot].k=k;
				a[tot].t=++t;
				a[tot].id=i;
			}
			else
			{
				int r,val;
				scanf("%d%d",&r,&val);
				a[++tot].op=2;
				a[tot].pos=r;
				a[tot].t=++t;
				a[tot].add=pos[r];
				
				a[++tot].op=1;
				a[tot].pos=r;
				a[tot].t=++t;
				a[tot].add=val;
				
				b[top++]=val;
				pos[r]=val;
			}
		}
		sort(b,b+top);
		top=unique(b,b+top)-b;
		for(int i=0;i<top;i++)
			mp[b[i]]=i+1;
		for(int i=1;i<=tot;i++)
			if(a[i].op!=3)
				a[i].add=mp[a[i].add];
		solve(1,t,1,top+1);
		for(int i=1;i<=m;i++)
			if(ANS[i]!=-1)
				printf("%d\n",b[ANS[i]-1]);
	}
}


整体二分AC代码:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值