Luogu4113 采花(树状数组)题解

题意

给出一个序列与若干个区间,求每一个区间内出现次数大于等于2的数的个数

算法

树状数组离线(连主席树都敢卡……

思路

求出现次数的题,基本都是这个套路 e.g.Luogu1972 HH的项链

考虑对于区间\(l-r\),每一种颜色只有倒数第二个对答案有影响(因为这是该颜色最晚可以被计入答案的位置,当询问的左端点超过这个值时,该颜色就没用了),因此维护一个\(nxt_{1-n}\),记录\(i\)号位置上的颜色的前一个相同颜色的位置,当某一颜色出现次数超过\(2\)时,就在\(nxt_i\)\(+1\)(注意不是在\(i\),因为只有它自己时不算入答案),并且在\(nxt_{nxt_i}\)\(-1\)来更新。同时,为了不对之前的询问造成影响,要先将答案离线下来并以右端点排序。

代码

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int maxn = 2e6 + 10;
int n,C,c[maxn],m,nxt[maxn],a[maxn],T[maxn],ans[maxn],vis[maxn];
struct Que{
    int l,r,id;
}q[maxn];

int lowbit(int x){return x & (-x);}

void add(int x, int val){
    for(int i = x; i <= n; i += lowbit(i))
        c[i] += val; 
}

int query(int x){
    int ans = 0;
    for(int i = x; i; i -= lowbit(i))
        ans += c[i];
    return ans;
}

bool cmp(Que x, Que y){return x.r < y.r;}

int main(){
    scanf("%d%d%d", &n, &C, &m);
    for(int i = 1; i <= n; ++ i) scanf("%d", a + i);
    for(int i = 1; i <= n; ++ i){
        if(T[a[i]]) nxt[i] = T[a[i]];
        T[a[i]] = i;
    } memset(T,0,sizeof(T));
    for(int i = 1; i <= m; ++ i) scanf("%d%d", &q[i].l, &q[i].r), q[i].id = i;
    sort(q + 1, q + 1 + m, cmp);
    int now = 0;
    for(int i = 1; i <= m; ++ i){
        while(now + 1 <= q[i].r){
            now ++; 
            if(T[a[now]] >= 1){
                if(nxt[nxt[now]]) add(nxt[nxt[now]], -1);
                add(nxt[now], 1);
            }
            else T[a[now]] += 1;
        } 
        ans[q[i].id] = query(q[i].r) - query(q[i].l - 1);
    }
    for(int i = 1; i <= m; ++ i) printf("%d\n", ans[i]);
    return 0;
}

总结

关于这类卡的很死的区间出现次数的问题,可以使用树状数组解答,主要思考如何统计对答案的影响并更新树状数组。

posted @ 2020-10-24 16:00  When_C  阅读(75)  评论(0编辑  收藏  举报