看花 :查询区间内不一样元素个数-预处理+树状数组

 

牛客网-看花 https://www.nowcoder.com/test/question/1d6f8e0e16de49d094d16057c92d49de?pid=17906015&tid=28876854node

小明有一个花园,花园里面一共有m朵花,对于每一朵花,都是不同的,小明用1~m中的一个整数表示每一朵花。ios

他很喜欢去看这些花,有一天他看了n次,并将n次他看花的种类是什么按照时间顺序记录下来。数组

记录用a[i]表示,表示第i次他看了a[i]这朵花。spa

小红很好奇,她有Q个问题,问[l,r]的时间内,小明一共看了多少朵不一样的花儿,小明由于在忙着欣赏他的花儿,因此想请你帮他回答这些问题。.net


输入描述:
输入两个数n,m;(1<=n<=2000,1<=m<=100);分别表示n次看花,m表示一共有m朵花儿。

接下来输入n个数a[1]~a[n],a[i]表示第i次,小明看的花的种类;

输入一个数Q(1<=Q<=1000000);表示小红的问题数量。

输入Q行 每行两个数l,r(1<=l<=r<=n);表示小红想知道在第l次到第r次,小明一共看了多少不一样的花儿。

输出描述:
一共Q行

每一行输出一个数 表示小明在[l,r]的时间内看了多少种花。

输入例子1:
5 3
1 2 3 2 2
3
1 4
2 4
1 5

输出例子1:
3
2
3

离线查询的思想。预处理:先将N个数字读取进来,用next[]数组储存每一个数字下一次出现的位置,fir[i]布尔数组表示从当前查询的位置到结尾第i个元素是否为第一次出现,那么区间[1,x]的fir[i]的和即为这个区间的数的个数,用一个树状数组便可。再将M次查询读取进来,按查询区间的左端点进行排序,而后开始,遍历M个查询的左端点,将区间之前出现过的数字用next[]数组转移至左端点之后,这样区间[左端点的坐标,x]的fir[i]的和即为这个区间的数的个数。再对这个区间进行查询。时间复杂度:预处理O(N + Mlog(M)) 查询O(Mlog(N)+N)

原文连接:https://blog.csdn.net/CZWin32768/article/details/47054947code

 

#include<iostream>
#include<algorithm>
#include<math.h>
#define ll long long
#define M 0x3f3f3f3f3f
using namespace std;
int n,m,q;
int a[50005],c[50005],ans[2000005],Next[50005],vis[1000005];
bool is_first[50005];
//a[]输入的n个数
//c[]树状数组求和
//ans[i]第i个查询的结果
//is_first[]判断第i个数是否出现过
//vis[]记录第i个数出现的位置
//Next[i]第i个数下一次要出现的位置

struct node
{
    int le;
    int ri;
    int pos;
}p[4000005];
bool cmp(node x,node y)//按左端点从小到大排序
{
    return x.le<y.le;
}
int lowbit(int x)
{
    return x&(-x);
}
void update(int x,int v)//更新is_first[x]的值,同时更新相应和
{
    while(x<=n)
    {
        c[x]=c[x]+v;
        x=x+lowbit(x);
    }
}
int getsum(int x)
{
    int sum=0;
    while(x>0)
    {
        sum=sum+c[x];
        x=x-lowbit(x);
    }
    return sum;
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    
    for(int i=n;i>=1;i--)//预处理第i个数出现的位置
    {
        if(!vis[a[i]])
        {
            vis[a[i]]=i;//标记出现的位置
            is_first[i]=true;
        }
        else
        {
            Next[i]=vis[a[i]];//数字a[i]下一次要出现的位置
            is_first[Next[i]]=false;
            is_first[i]=true;//更新状态
            vis[a[i]]=i;
        }
    }
    scanf("%d",&q);
    for(int i=1;i<=q;i++)
    {
        scanf("%d%d",&p[i].le,&p[i].ri);
        p[i].pos=i;
    }
    sort(p+1,p+q+1,cmp);
    for(int i=1;i<=n;i++)
    {
        if(is_first[i])//a[i]是第一次出现,维护树状数组(初始化)
            update(i,1);
    }
    int now=p[1].le;
    int k=1;
    for(int i=1;i<=q;i++)
    {
        for(;k<p[i].le;k++)//在[1,le)区间出现过的数置-1,再将这些数转移到在区间[len,n]出现的位置并置1
        {
            if(is_first[k])
            {
                update(k,-1);
                is_first[k]=false;
                if(Next[k])
                {
                    is_first[Next[k]]=true;
                    update(Next[k],1);
                }
            }
        }
        k=p[i].le;
        now=p[i].le;
        ans[p[i].pos]=getsum(p[i].ri)-getsum(p[i].le-1);
    }
    for(int i=1;i<=q;i++)
        printf("%d\n",ans[i]);
    return 0;
}