[SDOI2009][Bzoj1878] HH的项链 离线+树状数组

题目链接: Bzoj1878.

—————————————-

概述

题目大意如下.

给定一个长度为 n 的序列an, 有 m 个询问, 每一次询问区间[l,r]内有多少个不同的数字.
(1n50000,1m200000,1ai1000000)

—————————————-

分析

听说这是树状数组模板题? 那我还是太弱了..看了好久才发现可以用树状数组.

我们观察一下, 一次询问的答案是这段区间内数字的种类数, 但是有些数会重复出现许多次, 那么我们能不能让每一种数只提供一次贡献呢?

答案是可以的. 我们不妨先考虑暴力一点的做法, 对于每一个询问, 我们在区间里从左往右扫一遍, 假如当前这个数以前没有出现过, 就把这个位置的贡献加1, 假如以前出现过, 就把上一次出现的位置贡献减1, 再把当前位置的贡献加1, 这样就可以保证每一种数只会产生1的贡献. 最后统计这个区间内的贡献之和就行了.

乍一看这个做法似乎是毫无意义的, 因为你都已经从左往右扫了一遍了, 还那么麻烦的加1减1干什么? 直接用桶暴力去统计答案不是方便多了吗?

肤浅! 没看到上面是在单点修改区间查询嘛?
迂腐! 不知道有个套路叫做区间排序嘛?

咳咳, 我们按询问区间的右端点排序然后依次处理排序后的区间, 查询完第 i 个区间之后, 将第i+1个区间多出来的部分按照上面说的那样处理, 只不过由于是单点修改区间查询, 我们可以使用树状数组优化.

那么就这么愉快地分析完了这道模板题啦. 时间复杂度 O((n+m)logn).

—————————————-

代码

#include<bits/stdc++.h>
#define For(i, j, k) for(int i = j; i <= (int)k; ++ i)
using namespace std;

const int maxn = 50000 + 5;
const int maxm = 200000 + 5;

int n, m, tot;
int a[maxn], pre[maxn], c[maxn], Ans[maxm];
struct Que{
    int l, r, id;
    bool operator < (Que o)const{
        return r < o.r;
    }
}q[maxm];
map<int,int> mp;

inline int Read(){
    int x = 0;  char c = getchar();
    for(; !isdigit(c); c = getchar());
    for(; isdigit(c); c = getchar())    x = (x << 3 ) + (x << 1) + (c ^ 48);
    return x;
}

inline void add(int x, int v){
    for(; x < maxn; x += x & (-x))
        c[x] += v;
}

inline int sum(int x){
    int back = 0;
    for(; x; x -= x & (-x))
        back += c[x];
    return back;
}

int main(){
    file();

    n = Read();
    For(i, 1, n){
        a[i] = Read();
        mp[a[i]];
    }
    m = Read();
    For(i, 1, m){
        q[i].id = i;
        q[i].l = Read();  q[i].r = Read();
    }

    for(map<int,int>::iterator p = mp.begin(); p != mp.end(); ++ p)
        p->second = ++ tot;//这是一个优秀的离散化方法, 可以自己理解一下.
    For(i, 1, n)    a[i] = mp[a[i]];
    sort(q + 1, q + 1 + m);

    int now = 1, l, r;
    For(i, 1, m){
        l = q[i].l;  r = q[i].r;
        while(now <= r){
            if(pre[a[now]])    add(pre[a[now]], -1);
            add(pre[a[now]] = now, 1);
            ++ now;
        }
        Ans[q[i].id] = sum(r) - sum(l - 1);
    }

    For(i, 1, m)
        printf("%d\n", Ans[i]);
    return 0;
}

—————————————-

小结

这道模板题困扰了我还挺久的, 开始在想普通线段树, 后来发现维护不了, 于是想权值线段树, 结果发现理解错了题意… 后来冥思苦想半天才发现了我上面说的那个暴力方法, 用树状数组优化一下就可以了. 果然还是套路题做少了, 还要继续努力.

—————————————-

wrote by miraclejzd.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值