BZOJ 2120 数颜色 分块+二分

本文介绍了一种使用分块技术优化区间查询问题的方法,适用于含有大量不同数值的数组,并涉及频繁的区间查询与少量的单点修改操作。通过将数组分为多个块并维护每个块的状态信息,可以有效地减少查询时间复杂度。

点击打开链接

题意:n个数,m个操作n,m<=1e4 
1.查询[l,r]区间内有多少不同的数字
2.单点修改 (不超过1e3次)
如果利用线段树 单点更新时把x变成y后 不容易更新出[l,r]内不同数个数
此时用分块来做,记录每一个数它的上一个位置,不存在则记为0,判断[l,r]内不同的个数,如果a[i]第一次出现在[l,r]内,则ans++ 
更新次数<=1e3,则暴力更新n个数的b[i]和pre[i]即可,修改复杂度O(n) 
若查询区间[l,r] 头尾两块直接判断b[i]<l是否成立,对中间每块的b[i]排序后二分,找到最后一个b[i]<v的位置即可  查询复杂度(sqrt(n)*logn)

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+20;
const int M=1e6+20;
int n,m,a[N];
int num,block,pos[N],l[N],r[N]; 
//num为分块的个数,block为每块的大小,pos[i]为i属于哪一块
//l[i],r[i]分别为第i块左右边界
int b[N],last[M],pre[N];
//b,pre都是记录上一次出现位置 
void reset(int x)
{
	int l=(x-1)*block+1,r=min(n,x*block);
	for(int i=l;i<=r;i++)
		pre[i]=b[i];
	sort(pre+l,pre+r+1);//查询时用到
}
void build()
{
	block=int(sqrt(n)+log(2*n)/log(2));
	num=n/block;if(n%block) num++;

	for(int i=1;i<=n;i++)
	{
		b[i]=last[a[i]];//bi:i上一次出现位置 
		last[a[i]]=i;//ai,最后一次出现位置  
		pos[i]=(i-1)/block+1;	
	}
	for(int i=1;i<=num;i++)
	reset(i);
} 
int find(int x,int v)
{
	int l=(x-1)*block+1,r=min(x*block,n); 
	int first=l;
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(pre[mid]<v) l=mid+1;
		else r=mid-1;
	}
	return l-first;
}
int ask(int l,int r)
{
	int ans=0;
	if(pos[l]==pos[r])
	{
		for(int i=l;i<=r;i++)
			if(b[i]<l)//上一次出现在l之前 
			ans++;
	}
	else
	{
		//第一块后最后一块 
		for(int i=l;i<=block*pos[l];i++)
			if(b[i]<l) ans++;
		for(int i=block*(pos[r]-1)+1;i<=r;i++)
			if(b[i]<l) ans++;
	}
	//中间的块 
	for(int i=pos[l]+1;i<pos[r];i++)
		ans+=find(i,l);
	return ans;
}
void change(int x,int v)
{
	for(int i=1;i<=n;i++)
		last[a[i]]=0;
	a[x]=v;
	for(int i=1;i<=n;i++)//修改操作不多于1e3 
	{
		int t=b[i];
		b[i]=last[a[i]];
		if(t!=b[i]) reset(pos[i]);
		last[a[i]]=i;
	}
}
int main()
{
	
	while(cin>>n>>m)
	{
		for(int i=1;i<=n;i++)
			scanf("%d",&a[i]);
		build();
		char op[5];
		int x,y;
		for(int i=1;i<=m;i++)
		{
			scanf("%s%d%d",op,&x,&y);
			if(op[0]=='Q')
			{
				//[x,y]中有多少个不同的数
				printf("%d\n",ask(x,y));
			}
			else
			change(x,y);
		}
	}
	return 0;	
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值