bzoj1878(树状数组&&在线转离线)

题目链接:https://www.lydsy.com/JudgeOnline/problem.php?id=1878

Description

HH有一串由各种漂亮的贝壳组成的项链。HH相信不同的贝壳会带来好运,所以每次散步 完后,他都会随意取出一段贝壳,思考它们所表达的含义。HH不断地收集新的贝壳,因此, 他的项链变得越来越长。有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同 的贝壳?这个问题很难回答。。。因为项链实在是太长了。于是,他只好求助睿智的你,来解 决这个问题。

Input

第一行:一个整数N,表示项链的长度。 第二行:N个整数,表示依次表示项链中贝壳的编号(编号为0到1000000之间的整数)。 第三行:一个整数M,表示HH询问的个数。 接下来M行:每行两个整数,L和R(1 ≤ L ≤ R ≤ N),表示询问的区间。

Output

M行,每行一个整数,依次表示询问对应的答案。

Sample Input

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

Sample Output

2
2
4

HINT

对于20%的数据,N ≤ 100,M ≤ 1000;
对于40%的数据,N ≤ 3000,M ≤ 200000;
对于100%的数据,N ≤ 50000,M ≤ 200000。

 题解:

首先树状数组维护一段区间内的数的种类数。

然后就是在线转离线的经典做法,因为这里面没有涉及修改的,所有我们可以这样做,按照右端点从小到大对询问排序。

之后从左到右枚举 i ,同时把上次出现该数的位置的影响减去,加上当前数的影响。

具体看一个例子(来自HH的项链

当now_r=1,insert(1,1)表示在位置1出现了一个未重复的数,需要在树状数组上对应的位置+1,此时树状数组对应序列每个位置上的值为:1 0 0 0

当now_r=2,insert(2,1):1 1 0 0

当now_r=1,由于数a[3](就是1)之前出现过,最后一遍出现的地方为1,则insert(1,-1),insert(3,1),即减去之前位置加上的种类数转而在右边的位置+1,:0 1 1 0

当now_r=3,insert(4,1):0 1 1 1

这样,每次i更新时看是否i=一个r,while输出sum[l,r]=sum[r]-sum[l-1](询问[2,3]=sum[3]-sum[2-1]=2)

最后一点,为了记录每个数最后一遍出现过的位置,需要一个last数组。last[i]=0表示数i还未出现过,last[i]=k表示数i最后一次出现的位置为k。

详解请参考代码:

#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#include<cstdlib>
#include<vector>
#include<map>
#include<set>
#include<stack>
#include<queue>
#define PI atan(1.0)*4
#define e 2.718281828
#define rp(i,s,t) for (i = (s); i <= (t); i++)
#define RP(i,s,t) for (i = (t); i >= (s); i--)
#define ll long long
#define ull unsigned long long
#define mst(a,b) memset(a,b,sizeof(a))
#define push_back() pb()
#define fastIn                    \
    ios_base::sync_with_stdio(0); \
    cin.tie(0);
using namespace std;
inline int read()
{
    int a=0,b=1;
    char c=getchar();
    while(c<'0'||c>'9')
    {
        if(c=='-')
            b=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9')
    {
        a=(a<<3)+(a<<1)+c-'0';
        c=getchar();
    }
    return a*b;
}
inline void write(int n)
{
    if(n<0)
    {
        putchar('-');
        n=-n;
    }
    if(n>=10)
        write(n/10);
    putchar(n%10+'0');
}
const int N=1e6+7;
int n;
int a[N],ans[N],last[N],sum[N];
//a数组用来读入数据,ans存储查询结果
//last[a[i]]表示上一次a[i]出现的下标,不出现为0
//sum即维护的树状数组,表示一段区间内的种类数
struct node{
    int l,r,num;
}p[N];//存储每次查询的左右端点,以及查询的下标
int lowbit(int x){
    return x&(-x);
}
void update(int i,int x){//更新
    while(i<=n){
        sum[i]+=x;
        i+=lowbit(i);
    }
}
int query(int i){//查询
    int res=0;
    while(i>=1){
        res+=sum[i];
        i-=lowbit(i);
    }
    return res;
}
bool cmp(node p1,node p2){//按照查询的右端点排序
    return p1.r<p2.r;
}
//在线转离线的经典例题
int main(){
	n=read();
    int i;
    rp(i,1,n) a[i]=read();
    int m=read();
    rp(i,1,m) p[i].l=read(),p[i].r=read(),p[i].num=i;
    sort(p+1,p+1+m,cmp);//排序
    int nowqueues=1;
    //从左到右一个一个推和查询相同的r
    rp(i,1,n){
        update(i,1);//默认为位置上出现未重复的数
        //如果当前位置的数出现过,则之前出现的位置减去
        //保证每次查询的时候都是不重复的数
        if(last[a[i]]) update(last[a[i]],-1); 
        last[a[i]]=i;//更新a[i]最后一次出现的位置
        while(p[nowqueues].r==i&&nowqueues<=m){//如果和查询的右区间相同
            ans[p[nowqueues].num]=query(p[nowqueues].r)-query(p[nowqueues].l-1);
            //答案即为([1,r]-[1,l-1]),满足可减性,为什么呢?
            //因为我们已经把重复的都给去掉了。
            nowqueues++;
        }
    }
    rp(i,1,m)
        printf("%d\n",ans[i]);
	return 0;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值