bzoj2453/2120(分块)

给出一个数列,求一个区间不相同数字的个数,包含修改操作——最多1000次修改;数字最大是100W

 

用pre[i]记录前一个和i相同颜色的球的所在位置

询问l到r时,如果pre[i]<l说明在l到i这一段没用和i颜色相同的球,则ans++

利用这种思路我们可以。。。分块

每一块内按pre[i]排序,然后分块做就行了

 

不过正解是树套树,主流的思路还是一样,就是通过pre来做,只不过用不同数据结构维护罢了。

 

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
using namespace std;
const int N=12005;

int n,m,a[N],c[N],block,num;
int pre[N],head[N*100],pos[N];

void build()
{
	block=sqrt(n);
	num=block+ (block*block!=n);
	
	for (int i=1;i<=n;i++) pos[i]=(i-1)/block+1;
	
	for (int i=1;i<=n;i++)
	{
		a[i]=pre[i]=head[c[i]];
		head[c[i]]=i;
	}
	for (int i=1;i<=num;i++)
	{
		int l=(i-1)*block+1,r=min(i*block,n);
		sort(a+l,a+r+1);
	}
}
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(a[mid]<v)l=mid+1;
		else r=mid-1;
	}
	return l-first;
}
void query()
{
	int l,r,ans=0;
	scanf("%d%d",&l,&r);
	if (pos[l]==pos[r]) for (int i=l;i<=r;i++) ans+=(pre[i]<l);
	else 
		{
			for (int i=l;i<=min(pos[l]*block,n);i++) ans+=(pre[i]<l);//这里是pre,不能是a,因为a数组是按块排序以后的不是原数组,只是便于询问的辅助的数组。
			for (int i=(pos[r]-1)*block+1;i<=r;i++) ans+=(pre[i]<l);
			for (int i=pos[l]+1;i<=pos[r]-1;i++) 
			{
				int ll=(i-1)*block+1,rr=min(i*block,n);
				ans+=lower_bound(a+ll,a+rr+1,l)-a-ll;
			} 	
		}
	printf("%d\n",ans);
}
void up(int i)
{
	int ll=(i-1)*block+1,rr=min(i*block,n);
	for (int i=ll;i<=rr;i++) a[i]=pre[i];
	sort(a+ll,a+rr+1);
}
void updata()
{
	int x,k;
	scanf("%d%d",&x,&k);
	for (int i=1;i<=n;i++) head[c[i]]=0;
	c[x]=k;
	
	for (int i=1;i<=n;i++)
	{
		int tmp=pre[i];
		pre[i]=head[c[i]];
		head[c[i]]=i;
		if (tmp!=pre[i]) up(pos[i]);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++) scanf("%d",&c[i]);
	build();
	
	char ch[2];
	while (m--)
	{
		scanf("%s",ch);
		if  (ch[0]=='Q') query();
		else updata();
	}
	return 0;
}

总结

1:注意在用stl的时候,一定要注意是左闭右开区间,即右端点要加一,才能成为右开(这里wa了好几次)

2:这里是用pos【i】,比较主流的写法。

3:注意当最后一个区间不是整区间时,右端点取n,不是i*block,就是在取区间的时候注意右端点取min。

4:这里通过pre【i】维护上一个数出现的位置,来处理不相同的个数。思路真的好巧妙,然后因为更新次数少,所以单次更新的复杂度可以高一点,也就是分块有的时候处理还是很暴力的(这里每次更新有一些都要重新计算)。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值