牛客网暑期ACM多校训练营(第一场)J 题解

这题是这场的签到题,每次查询求a[1...l]和a[r...n]有多少种不同的数字
这题做法很多,有***树、莫队、离线树状数组。
***树和莫队都是很多人T了的,包括我们队在内,这里我就讲一下离线树状数组的做法。
首先将原数组扩展为2倍长的,即 a[i+n] = a[i]
对于查询a[1...l]和a[r..n]有多少种不同的数字可以转换为查询 a[r...l+n]有多少种不同的数字
首先考虑维护一个前缀和,pre[i]表示a[1...i]有多少种不同的数字,那么对于a[l...r]的答案就为pre[r] - pre[l-1] + 在a[l...r]和a[1...l-1]同时出现的数字的种类
对于如何求在a[l...r]和a[1...l-1]同时出现的数字的种类,可以考虑使用树状数组维护,树状数组的第i个点表示a[i]是否已经在1...l出现过,对于每个查询只需要查树状数组中l~r的区间和即可
那么我们只要对区间查询进行排序,对于左端每次右移的时候把对应的数的下一个位置加入到数状数组中即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;

int n, q;
int a[N];
int pre[N];     //pre[i]表示数组前i个数有多少种不同的数
bool vis[N];
int last[N];    //用来计算nxt数组用的辅助数组,记录每个数上一次出现的位置
int nxt[N];     //用来维护数组中每个位置的数下一次出现的位置
int ans[N];

struct Q{
    int l, r, id;
    bool operator < (const Q &b) const {    //将查询按照左端点排序
        return this->l < b.l;
    }
}query[N];

int bit[N];
int lowbit(int x){
    return x & -x;
}
void update(int x, int y){
    for(int i = x; i < N; i += lowbit(i)){
        bit[i] += y;
    }
}

int Query(int x){
    int ans = 0;
    for(int i = x; i > 0; i -= lowbit(i)){
        ans += bit[i];
    }
    return ans;
}

int Query(int l, int r){
    return Query(r) - Query(l-1);
}


int main(){
#ifdef local
    freopen("in","r",stdin);
#endif
    while(scanf("%d%d", &n, &q)!=EOF){
        memset(bit, 0, sizeof(bit));
        memset(vis, 0, sizeof(vis));
        memset(nxt, -1, sizeof(nxt));
            memset(last, -1, sizeof(last));
        for(int i = 1; i <= n; ++i){
            scanf("%d", &a[i]);
            a[i+n] = a[i];
        }
        n *= 2;
        pre[0]=0;
        for(int i = 1; i <= n; ++i){
            if(!vis[a[i]]){
                vis[a[i]] = true;
                pre[i] = pre[i-1] + 1;
            }
            else{
                pre[i] = pre[i-1];
            }
            if(~last[a[i]]){
                nxt[last[a[i]]] = i;
            }
            last[a[i]] = i;
        }
        for(int i = 0; i < q; ++i){
            scanf("%d%d", &query[i].l, &query[i].r);
            query[i].l += n/2;
            swap(query[i].l, query[i].r);
            query[i].id = i;
        }
        sort(query, query+q);
        int nowl = 1;
        for(int i = 0; i < q; ++i){
            while(nowl < query[i].l){
                if(~nxt[nowl]){
                    update(nxt[nowl], 1);
                }
                ++nowl;
            }
            ans[query[i].id] = pre[query[i].r] - pre[query[i].l-1] + Query(query[i].l, query[i].r);
        }
        for(int i = 0; i < q; ++i){
            printf("%d\n",ans[i]);
        }
    }
    return 0;
}

全部评论
胖队优秀
点赞 回复 分享
发布于 2018-07-20 16:17
弱弱的问一句,为什么相同的数字不会重复记到数组里 QAQ
点赞 回复 分享
发布于 2018-07-20 16:42
请问树状数组求和表示的是1~l中出现的不同种类的数字的个数吗?
点赞 回复 分享
发布于 2018-07-20 20:01
if(~last[a[i]]){ nxt[last[a[i]]] = i; } 你好 请问第65行这里的代码是否可以直接写为 nxt[last[a[i]]] = i; 因为 last[] 数组的初值是0  不会取到-1
点赞 回复 分享
发布于 2018-07-20 20:39

相关推荐

11-01 20:03
已编辑
门头沟学院 算法工程师
Amazarashi66:这种也是幸存者偏差了,拿不到这个价的才是大多数
点赞 评论 收藏
分享
沉淀一会:**圣经 1.同学你面试评价不错,概率很大,请耐心等待;2.你的排名比较靠前,不要担心,耐心等待;3.问题不大,正在审批,不要着急签其他公司,等等我们!4.预计9月中下旬,安心过节;5.下周会有结果,请耐心等待下;6.可能国庆节前后,一有结果我马上通知你;7.预计10月中旬,再坚持一下;8.正在走流程,就这两天了;9.同学,结果我也不知道,你如果查到了也告诉我一声;10.同学你出线不明朗,建议签其他公司保底!11.同学你找了哪些公司,我也在找工作。
点赞 评论 收藏
分享
不愿透露姓名的神秘牛友
11-21 17:16
科大讯飞 算法工程师 28.0k*14.0, 百分之三十是绩效,惯例只发0.9
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务