[BZOJ 1878] [SDOI2009] HH的项链

题目链接: BZOJ - 1878

 

题目分析

题目的询问是某个区间内的颜色种类数,所以我们希望这个区间内的每种颜色只被计数一次,那么我们就选取询问区间内的每种颜色第一次出现的元素计数,之后再出现已经在询问区间中出现过的颜色就不再计数。考虑一种离线算法,如果我们将所有询问按照询问区间的左端点排序,那么所有询问的左端点就是不递减的,一直向右推移。开始时预处理出每个元素后面第一个与它颜色相同的元素是哪一个,并将所有出现的颜色的第一个元素加入到树状数组中。那么开始时维护的区间就是从 1 开始的。每次处理一个询问,如果当前维护的区间左端点比询问的区间左端点靠左,那么就应该将左端点向右推移到询问的左端点。每次将左端点向右移动一格,一个元素被移除,这时它一定是它的颜色在此时维护的区间中的第一个元素,所以它一定存在于树状数组中,那么我们就将它在树状数组中删除,再将它的下一个与它同色的元素加入到树状数组中。然后对于以现在维护的左端点 L 为左端点的询问 (L, R),只要用树状数组查询R的前缀和 Get(R) 就是答案,这个前缀和中所有的元素都是从 L 开始的区间中某种颜色第一次出现的位置,不会有某种颜色重复计数的情况。

代码

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
#include <cmath>
 
using namespace std;
 
const int MaxN = 50000 + 5, MaxQ = 200000 + 5, MaxType = 1000000 + 5;
 
int n, m, a, b;
int A[MaxN], Next[MaxN], T[MaxN], Last[MaxType], Ans[MaxQ];
 
struct Query
{
    int l, r, num;
} Q[MaxQ];
 
bool cmp(Query q1, Query q2) {
    return q1.l < q2.l;
}
 
void Add(int x, int num) {
    for (int i = x; i <= n; i += i & (-i)) 
        T[i] += num; 
}
 
int Get(int x) {
    int ret = 0;
    for (int i = x; i >= 1; i -= i & (-i)) 
        ret += T[i];
    return ret;
}
 
int main() 
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) scanf("%d", &A[i]);
    scanf("%d", &m);
    for (int i = 1; i <= m; i++) {
        scanf("%d%d", &a, &b);
        Q[i].l = a; Q[i].r = b;
        Q[i].num = i;
    }
    sort(Q + 1, Q + m + 1, cmp);
    memset(T, 0, sizeof(T));
    memset(Last, 0, sizeof(Last));
    for (int i = 1; i <= n; i++) {
        if (Last[A[i]] == 0) Add(i, 1);
        else Next[Last[A[i]]] = i;
        Last[A[i]] = i;
    }
    int x = 1;
    for (int i = 1; i <= m; i++) {
        while (x < Q[i].l) {
            Add(x, -1);
            if (Next[x]) Add(Next[x], 1);
            ++x;
        }
        Ans[Q[i].num] = Get(Q[i].r);
    }
    for (int i = 1; i <= m; i++) printf("%d\n", Ans[i]);
    return 0;
}

 

posted @ 2014-12-18 22:49  JoeFan  阅读(242)  评论(0编辑  收藏  举报