牛客网暑期ACM多校训练营(第一场)J——Different Integers 区间不同数

题意:

给你一个n个数的数列和q次询问l, r,需要回答a[1]...a[l], a[r]...a[n]的不同数的个数。

n,q <=1e5,a[i] <= n

题解:

用莫队的写法比较简单,这里不再多说。

说一下树状数组的写法。普通的求区间不同数可以用离线树状数组,方法是按查询的r从小到大排序, 然后遍历整个数组,用树状数组记录这个值目前为止最后出现的位置,在这个位置加一, 并且边更新询问的指针。查询只用sum(r) - sum(l-1)即可。

这个题有点不同。但是我们可以求出a[1]...a[l], a[r]...a[n]里面没有出现的数的个数,即求出a[l+1]....a[r]里面出现且不在其他位置出现的值的个数。

现在我们的查询相当于询问a[l]....a[r]里面出现的且不在外面出现的不同数的个数。

我们额外增加一个数组first保存每个数第一次出现的位置。

方法还是按照查询的r从小到大排序,遍历整个数组,如果当前位置i是a[i]的最后位置,那么我们只需要在他第一次出现的位置处用树状数组+1.这样查询区间和就是在这个区间内出现且不在其他位置出现的不同数的个数了。

代码:

#include<bits/stdc++.h>
#define fir first
#define sec second
#define pii pair<int,int>
#define debug(x) cout<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
const int MAXN = 1e5 + 5;

int fir[MAXN], last[MAXN];
int tree[MAXN];
int n, q;

struct Query {
    int l, r;
    bool operator < (const Query & x) const {
        return r < x.r;
    }
    int id;
} que[MAXN];


void add(int i, int v) {
    while(i <= n) {
        tree[i] += v;
        i += i&-i;
    }
}

int query(int i) {
    int ret = 0;
    while(i) {
        ret += tree[i];
        i -= i&-i;
    }
    return ret;
}

int ans[MAXN];
int a[MAXN];


int main() {
#ifdef LOCAL
    freopen("input.txt", "r", stdin);
#endif
    while(~scanf("%d %d", &n, &q)) {
        memset(fir, 0, sizeof(fir));
        memset(tree, 0, sizeof(tree));
        int tot = 0;
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            if(!fir[a[i]]) fir[a[i]] = i, tot++;
            last[a[i]] = i;
        }
        for(int i = 1; i <= q; i++) scanf("%d %d", &que[i].l, &que[i].r), que[i].id = i;
        sort(que + 1, que + 1 + q);
        int p = 1;
        for(int i = 1; i <= n; i++) {
            while(p <= q && que[p].r == i) {
                int id = que[p].id;
                ans[id] = tot - query(i) + query(que[p].l);
                p++;
            }
            if(last[a[i]] == i) add(fir[a[i]], 1);
        }
        for(int i = 1; i <= q; i++) printf("%d\n", ans[i]);
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值