【HZOI】 赏花

Description

衡水湖为了迎接国际马拉松比赛,在跑道旁种了一排花,每朵花都有一种颜色色,共有N 朵。 
现在 sky 和 leaf 到衡水湖赏花,leaf 总会问 sky 一些问题,比如第l朵花到第r朵花之间能看到多少种颜色的花? 
sky 算得木有那么快,于是请你来帮帮他。

Input

第一行一个数N 
接下来一行N 个数Ci,表示花的颜色 
接下来一行一个数M 
接下来M行,每行两个数l,r,表示 leaf 的一个问题,询问l到r之间有多少种颜色的花。

Output

M行,每行表示 leaf 一个问题的答案

Sample Input

6
1 2 3 4 3 5
3
1 2
3 5
2 6

Sample Output

2
2
4

Hint

N≤100000,M≤200000,1≤Ci≤100000


【分析】

        看完题目,第一反应是尽管提问次数很多,但没有修改操作。于是便可以离线处理了。离线处理也就意味着预处理,那空间换时间。那么怎么换呢?可以用树状数组来维护一个前缀和。具体步骤如下:

      1.给提问按照左区间端点为第一关键字,右区间端点为第二关键字(右区间排序可有可无),均从小到大排序。

      2.从后往前所有相同的颜色拉成一个链表(像存边一样,一个last数组和一个next数组)。然后将所有的last即每个颜色从左边第一次出现的位置置为1(同时树状数组也修改)。

      3.查询区间时,将当前区间左端点左边的所有的1全部移至相应的next位置。原位置改为0,next位置改为1。那么这段区间的答案便是get(y)-get(x-1)。get表示利用树状数组求前缀和。

    再看看这种做法,其实质也就相当于先让每种颜色只出现在最左边的位置。然后随着区间的移动(排序后的),颜色依次出现在下一个位置。然后原位置标记为空,来保证一种颜色不会重复计算。而树状数组只不过是维护动态的前缀和罢了。

(PS:注意输出答案的时候,把顺序调整回来)


【代码】

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#include<cmath>
#include<iostream>
#include<algorithm> 
#define _lowbit(x) (x&(-x))
using namespace std;
int N,M,v[100005],qx[200005],qy[200005],rank[200005],ans[200005];
int c[100005],last[100005],next[100005];
int maxv=0;
void _qst_rank(int l,int r)
{
	int i=l,j=r,mr=rank[(i+j)>>1];
	while(i<=j)
	{
		while(rank[i]<mr) i++;
		while(rank[j]>mr) j--;
		if(i<=j)
		{
			swap(rank[i],rank[j]);
			swap(ans[i],ans[j]);
			i++;j--;
		}
	}
	if(l<j) _qst_rank(l,j);
	if(i<r) _qst_rank(i,r);
}
void _qst_qxqy(int l,int r)
{
	int i=l,j=r,mx=qx[(i+j)>>1],my=qy[(i+j)>>1];
	while(i<=j)
	{
		while((qx[i]<mx)||(qx[i]==mx&&qy[i]<my)) i++;
		while((qx[j]>mx)||(qx[j]==mx&&qy[j]>my)) j--;
		if(i<=j)
		{
			swap(qx[i],qx[j]);
			swap(qy[i],qy[j]);
			swap(rank[i],rank[j]);
			i++;j--;
		}
	}
	if(l<j) _qst_qxqy(l,j);
	if(i<r) _qst_qxqy(i,r);
}
void _in(int &x)
{
	char t=getchar();
	while(t<'0'||'9'<t) t=getchar();
	for(x=t-'0',t=getchar();'0'<=t&&t<='9';x=x*10+t-'0',t=getchar());
}
void _out(int x,int j=1)
{
	char t[100];
	if(x==0) putchar('0');
	for(;x;x/=10,j++) t[j]=x%10+'0';
	for(j--;j;j--) putchar(t[j]);
	putchar('\n');
}
void _init()
{
	_in(N);
	for(int i=1;i<=N;i++)
	{
	    _in(v[i]);
	    maxv=max(maxv,v[i]);
	}
	_in(M);
	for(int i=1;i<=M;i++)
	{
		_in(qx[i]);
		_in(qy[i]);
		rank[i]=i;
	}
}
void _solve()
{
	for(int i=N;i;i--)    //逆序建立链表 
	{
		next[i]=last[v[i]];     
		last[v[i]]=i;
	}
	_qst_qxqy(1,M);     //将问题排序 
	for(int i=1;i<=maxv;i++)
	    if(last[i]!=0)
	        for(int j=last[i];j<=N;j+=_lowbit(j))
	            c[j]++;
	for(int i=1;i<=M;i++)
	{
		for(int k=max(1,qx[i-1]);k<qx[i];k++)  
		{
			for(int j=k;j<=N;j+=_lowbit(j))   //删除之前的标记 
			    c[j]--;
		    if(next[k]!=0)    //如果可以移动,则移至下一位置 
		        for(int j=next[k];j<=N;j+=_lowbit(j))
		            c[j]++;
		}
		int temp=0;     //计算答案 
		for(int j=qx[i]-1;j;j-=_lowbit(j))
		    temp-=c[j];
		for(int j=qy[i];j;j-=_lowbit(j))
		    temp+=c[j];
		ans[i]=temp;
	}
	_qst_rank(1,M);      //按输入顺序排序 
	for(int i=1;i<=M;i++)
	    _out(ans[i]);
}
int main()
{
	_init();
	_solve();
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值