求区间不同元素种类数 2018/7/19 训练日记 牛客网多校第一场 离线+树状数组

今天做了牛客网第一次多校训练,感觉没什么手感,敲J题敲了好多遍

线段树,主席树,树状数组,莫队敲了一遍,三个超时,只有树状数组躺过,不知道为啥。。。。。。,后来线段树又改了一遍

先说树状数组的思路

首先应用离线方法,所谓离线,就在把所有询问先存贮起来,预处理之后再一个一个操作

像这个题目,每个操作要求区间不同元素的个数,直接去查的话,某个元素在之前如果出现了,我把他算在当前区间也不好,算在之前的区间也不好,都会出错。而且可能超时。

一个好的方法就是把区间排好序,针对某个区间在树状数组上更新以及查询相应值,这样能准确查出结果,但又不影响之后的查询。

具体来说,先把区间按右端点进行排序,然后从小区间开始,然后树状数组的含义就是指以当前r为结尾的前缀区间的元素种类数就是我当前扫到l _ r区间,把l - r区间还没在树状数组上更新的值,更新一遍,在之前已经存在了的值先删掉再更新一遍,确保我确定的元素都是往r靠的,这样才能保证求取区间正确

所以这就是这个离线用法的妙处,尤其要理解树状数组是:当前以r结尾的区间的元素种类个数,为了维护这个值的准确性,必须把没出现过的,加入到树状数组中,之前已经出现过了并且再次出现的,以再次出现的位置为准。 

而且每次对区间不用一开始就扫,每个就只要扫一次就可以了,用个map记录哪个出现了,出现在什么位置,就不用重新扫了,否则会超时。

上代码:QAQ

树状数组:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>
using namespace std;
const int N = 300010;
int n;
map<int,int>mp;
struct BIT
{
    int c[N];
    void init(int n)
    {
        for (int i=0;i<=2*n;i++) c[i]=0;
    }
    void add(int loc,int v)
    {
        while (loc<=2*n)
        {
            c[loc]+=v;
            loc+=loc&(-loc);
        }
    }
    int sum(int loc)
    {
        int ret=0;
        while (loc){
            ret+=c[loc];
            loc-=loc&(-loc);
        }
        return ret;
    }
}T;
struct node
{
    int l,r,id;
    bool operator <(const node&rhs) const{
        //if (l==rhs.l) return r<rhs.r;
        return r<rhs.r;
    }
}query[1000000+10];
int ans[1000000+10];
int A[N];
int main()
{
    while (scanf("%d",&n)!=EOF)
    {
        int q,w=0;
        scanf("%d",&q);
        mp.clear();
        T.init(n);
        for (int i=1;i<=n;i++)
        {
         scanf("%d",&A[i]);
         A[n+i]=A[i];
        }
        int temp;
        for (int i=0;i<q;i++)
        {
            scanf("%d%d",&query[i].l,&query[i].r);
            query[i].id=i;
            temp=query[i].l;
            query[i].l=query[i].r;
            query[i].r=n+temp;
        }
        sort(query,query+q);
        int cur=1;
        for (int i=0;i<q;i++){
            for(int j=cur;j<=query[i].r;j++){
                if (mp.find(A[j])!=mp.end()){
                    T.add(mp[A[j]],-1);
                }
                T.add(j,1);
                mp[A[j]]=j;
            }
           cur=query[i].r+1;
           ans[query[i].id]=(T.sum(query[i].r)-T.sum(query[i].l-1));
        }
        for (int i=0;i<q;i++){
            printf("%d\n",ans[i]);
        }
    }
    return 0;
}
线段树:

#include<bits/stdc++.h>
using namespace std;
#define lson l , m , rt << 1
#define rson m + 1 , r , rt << 1 | 1
const int maxn = 200005;
int sumv[maxn<<2];
void PushUP(int rt) {
    sumv[rt] = sumv[rt<<1] + sumv[rt<<1|1];
}
void update(int p,int sc,int l,int r,int rt) {
    if (l == r) {
        sumv[rt] += sc;
        return ;
    }
    int m = l + ((r - l)>>1);
    if (p <= m) update(p , sc , lson);
    else update(p , sc , rson);
    PushUP(rt);
}
 
int query(int L,int R,int l,int r,int rt) {
    if (L <= l && r <= R) {
        return sumv[rt];
    }
    int m = l + ((r - l)>>1);
    int ret = 0;
    if (L <= m) ret += query(L , R , lson);
    if (R > m) ret += query(L , R , rson);
    return ret;
}
struct Interval{
    int l, r, id;
    bool operator < (const Interval& other) const{
        return this->r < other.r;
    }
};
Interval sec[200005];
map<int, int> mp;
int arr[maxn];
int pre[maxn];
int ans[maxn];
int main()
{
    int n,cnt,m;
    int temp;
    while(~scanf("%d%d", &n,&m))
    {
        cnt=0;
        mp.clear();
        memset(sumv,0,sizeof(sumv));
        for(int i=1; i<=n; i++){
        scanf("%d", &arr[i]);
        if(!mp.count(arr[i]))
            mp[arr[i]] = cnt++;//区间上的每一个数都有一个独特的编号,使用map来进行映射,目的是方便判断此数是否已经重复出现过
        }
        for(int i=1;i<=n;i++)
            arr[i+n]=arr[i];
        for(int i=1; i<=m; i++){
            scanf("%d %d", &sec[i].r, &sec[i].l);
            sec[i].r+=n;
            sec[i].id = i;//因为后续有排序操作,而我们又要对问询答案顺序给出,所以记录给出的顺序,方便输出答案
        }
 
        sort(sec+1, sec+1+m);//按照右端点排序
        bool vis[maxn];
        memset(vis, false, sizeof(vis));
        n*=2;
        for(int i=1, j=1; j<=m && i<=n; i++){
            int tmp = mp[arr[i]];//拿出这个数的编号
            if(vis[tmp]){//如果这个数在前面已经访问过
                update(pre[tmp], -1, 1, n, 1);//将之前的位置信息抹去
                update(i, 1, 1, n, 1);//当前的位置才是有效的,所以给当前位置+1
                pre[tmp] = i;//更新pre数组方便下次操作
            }else{
                vis[tmp] = true;
                update(i, 1, 1, n, 1);
                pre[tmp] = i;
            }
            while(sec[j].r == i){
                ans[sec[j].id] = query(sec[j].l, sec[j].r, 1, n, 1);
                j++;
            }
        }
        //temp=ans[m+1];
        //cout<<temp<<endl;
        for(int i=1; i<=m; i++)
        {
 
                printf("%d\n", ans[i]);
 
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值