题目链接:http://acm.zzu.edu.cn:8000/problem.php?id=10508
题目大意:给定一个序列,长度为N,每次询问为一组区间[Li,Ri],输出Li到Ri中出现恰好两次的不同数的个数. N,M<=2*10^5,序列中元素<=10^9
解题思路:考虑用树状数组解决(大概是一种类型的题目)。树状数组一般用来快速计算更新(logN)前缀和,而对于本题来说,出现次数显然不能单纯随意相加相减,另外,对于右区间靠前的查询来说,对其查询之后后面的数据更新是不会再影响到它的,因此可以离线处理,并且需要在更新的时候针对重复元素进行一些处理。
首先考虑一个元素在序列中不同位置重复出现的情况,如下:
_ x _ x _ x _ x _ x _ (下划线表示出现了若干与x不相同的数字)
从前到后给每个x编号1,2,3,4,5,下面看一下从前到后扫面到这五个位置时如何更新(其中a,b等字母表示这个位置应当具有的值):
_ x _ x _ x _ x _ x _
1 0
2 a b 那么应当有 b + a = 1, (b + a) - a = 0, 则 b = 0, a = 1
3 a b c 那么应当有 c + b + a = 0, (c + b + a) - (b + a) = 0, (c + b + a) - a = 1, 则 c = 0, b = 1, a = -1.
4 a b c d 那么应当有 d + c + b + a = 0, d + c + b + a - (c + b + a) = 0, (d + c + b + a) - (b + a) = 1
(d + c + b + a) - a = 0, 则 d = 0, c = 1, b = -1, a = 0
...
即是:
_ x _ x _ x _ x _ x _
1 0
2 1 0
3 -1 1 0
4 0 -1 1 0
5 0 0 -1 1 0
然后关系就非常明显了,我们只需要记录下每个位置的数字上次出现的位置,然后 lastpos + 1,la_lastpos - 2, la_la_lastpos + 1, 即可。那么对于任意一个区间来说,由于其中每个数字都满足互相加减的条件,因此直接树状数组相加减即可。
大致过程:记录每个位置对应数字上次出现位置;将查询的区间按照有端点排序;从1~N枚举每个位置,按上述方法更新树状数组,然后计算以这个位置为右端点结束的区间的值。
代码:
1 const int maxn = 2e5 + 10; 2 struct node{ 3 int l, r, id; 4 bool operator < (const node& t) const{ 5 return r < t.r; 6 } 7 }; 8 node range[maxn]; 9 int n, m; 10 int a[maxn], ans[maxn], bit[maxn]; 11 int last[maxn]; 12 map<int, int> mmp; 13 14 int lowbit(int x){ 15 return x & (-x); 16 } 17 void add(int x, int v){ 18 while(x <= n){ 19 bit[x] += v; 20 x += lowbit(x); 21 } 22 } 23 int sum(int x){ 24 int ans = 0; 25 while(x > 0){ 26 ans += bit[x]; 27 x -= lowbit(x); 28 } 29 return ans; 30 } 31 void solve(){ 32 memset(last, 0, sizeof(last)); 33 memset(bit, 0, sizeof(bit)); 34 for(int i = 1; i <= n; i++){ 35 last[i] = mmp[a[i]]; 36 mmp[a[i]] = i; 37 } 38 sort(range + 1, range + 1 + m); 39 int ind = 1; 40 for(int i = 1; i <= n; i++){ 41 if(last[i] != 0){ 42 int la = last[i]; 43 add(la, 1); 44 if(last[la] != 0){ 45 int lla = last[la]; 46 add(lla, -2); 47 if(last[lla] != 0) 48 add(last[lla], 1); 49 } 50 } 51 while(ind <= m && range[ind].r == i){ 52 int tml = range[ind].l, tmr = range[ind].r; 53 ans[range[ind].id] = sum(tmr) - sum(tml - 1); 54 ind++; 55 } 56 } 57 for(int i = 1; i <= m; i++){ 58 printf("%d\n", ans[i]); 59 } 60 } 61 int main(){ 62 scanf("%d %d", &n, &m); 63 for(int i = 1; i <= n; i++) 64 scanf("%d", a + i); 65 for(int i = 1; i <= m; i++){ 66 scanf("%d %d", &range[i].l, &range[i].r); 67 range[i].id = i; 68 } 69 solve(); 70 }
题目:
10508: 数列游戏IV
Time Limit: 1 Sec Memory Limit: 128 MB
Submit: 32 Solved: 6
[Submit][Status][Web Board]
Description
给定一个序列,长度为N,每次询问为一组区间[Li,Ri],输出Li到Ri中出现恰好两次的不同数的个数.
Input
第一行两个整数N和M,N表示序列长度,M表示询问次数.(N,M<=2*10^5)
第二行N个整数,表示序列.(序列中元素<=10^9)
以后M行,每行为Li和Ri,表示询问区间.(1<=Li<=Ri<=N)
Output
对于每组询问,输出一行一个整数,表示不相同数的个数.
Sample Input
5 1 1 2 1 1 1 1 3
Sample Output
1