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;
}
总结
关于这类卡的很死的区间出现次数的问题,可以使用树状数组解答,主要思考如何统计对答案的影响并更新树状数组。