Luogu4113 [HEOI2012]采花
题目大意:给定一个长度为$n$的序列$a_i$,$m$次询问,每次询问$[l,r]$,求在区间内有多少个数出现了至少2次。
数据范围:$1\leq l\leq r\leq n\leq 2*10^6,1\leq m,a_i\leq 2*10^6$
首先我们考虑设$pre_i$表示上一个与$a_i$相等的位置。
所以$a_i$对答案有贡献当且仅当$l\leq pre_i<i\leq r$
这是一个经典的二维数点问题,cdq分治可以解决,但是。。。
所以我们换一种更优的做法,我们每次遍历到一个位置$a_i$的时候,考虑如果$l\leq pre_i<i\leq r$的时候,$i$会有贡献。
设$c_i$表示当前每个位置的贡献,所以对于上面的情况,令$c_{pre_{pre_i}}--,c_{pre_i}++$,因为这时之前要求$l\leq pre_{pre_i}$,现在只用$l\leq pre_i$了。
如果$r=i$,那么答案就是$\sum_{i=l}^rc_i$。所以可以离线下来之后用树状数组维护。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 #define Rint register int 3 using namespace std; 4 const int N = 2000003; 5 int n, mx, m, a[N], num[N], lst[N], ans[N], now = 1; 6 struct Query { 7 int l, r, id; 8 inline bool operator < (const Query &o) const {return r < o.r;} 9 } q[N]; 10 int c[N]; 11 inline int lowbit(int x){return x & -x;} 12 inline void change(int pos, int val){ 13 while(pos <= n){ 14 c[pos] += val; 15 pos += lowbit(pos); 16 } 17 } 18 inline int query(int pos){ 19 int res = 0; 20 while(pos){ 21 res += c[pos]; 22 pos -= lowbit(pos); 23 } 24 return res; 25 } 26 int main(){ 27 scanf("%d%d%d", &n, &mx, &m); 28 for(Rint i = 1;i <= n;i ++){ 29 scanf("%d", a + i); 30 lst[i] = num[a[i]]; 31 num[a[i]] = i; 32 } 33 for(Rint i = 1;i <= m;i ++){ 34 scanf("%d%d", &q[i].l, &q[i].r); 35 q[i].id = i; 36 } 37 sort(q + 1, q + m + 1); 38 for(Rint i = 1;i <= m;i ++){ 39 while(now <= q[i].r){ 40 if(lst[now]) change(lst[now], 1); 41 if(lst[lst[now]]) change(lst[lst[now]], -1); 42 ++ now; 43 } 44 ans[q[i].id] = query(q[i].r) - query(q[i].l - 1); 45 } 46 for(Rint i = 1;i <= m;i ++) 47 printf("%d\n", ans[i]); 48 }