Different Integers(树状数组)
描述
传送门:我是传送门
Given a sequence of integers a1,a2,…,ana1,a2,…,an and qq pairs of integers (l1,r1),(l2,r2),…,(lq,rq)(l1,r1),(l2,r2),…,(lq,rq), find count(l1,r1),count(l2,r2),…,count(lq,rq)count(l1,r1),count(l2,r2),…,count(lq,rq)where count(i,j)count(i,j) is the number of different integers among a1,a2,…,ai,aj,…,ana1,a2,…,ai,aj,…,an.
输入
The input consists of several test cases and is terminated by end-of-file. The first line of each test cases contains two integers nn and qq.
The second line contains n integers a1,a2,…,ana1,a2,…,an. The i-th of the following qq lines contains two integers lili and riri.
输出
For each test case, print qq integers which denote the result.
样例
输入
3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3
输出
2
1
3
Note
- 1≤n,q≤1051≤n,q≤105
- 1≤ai≤n1≤ai≤n
- 1≤li,ri≤n1≤li,ri≤n
- The number of test cases does not exceed 10.
思路
牛客多校赛第一场的签到题,但是很遗憾当时树状数组不太会用,没能写出来,当时看其他人写的博客也是没能彻底搞懂.
现在尝试在scaufat的题解牛客网暑期ACM多校训练营(第一场)J 题解 的基础上重新捋一下思路,添加一些代码注释
希望能够对树状数组的灵活应用有更深的理解.
我认为整个题目最关键的地方在于这两个地方
维护一个前缀和,pre[i]表示a[1…i]有多少种不同的数字,那么对于a[l…r]的答案就为pre[r] - pre[l-1] + 在a[l…r]和a[1…l-1]同时出现的数字的种类
左端每次右移的时候把对应的数的下一个位置加入到数状数组中
代码
1 #include <bits/stdc++.h> 2 using namespace std; 3 const int N = 2e5+10; 4 #define clr(a, x) memset(a, x, sizeof(a)) 5 int n,q; 6 int a[N]; 7 int pre[N]; // pre[i]表示数组前i个数有多少种不同的数 8 bool vis[N]; 9 int last[N]; // 用来计算nxt数组用的辅助数组,记录每个数上一次出现的位置 10 int nxt[N]; // 用来维护数组中每个位置的数下一次出现的位置 11 int ans[N]; 12 13 struct node 14 { 15 int l,r,id; 16 bool operator < (const node &b) const // 将查询按照左端点排序 17 { 18 return this->l < b.l; 19 } 20 }query[N]; 21 22 int bit[N]; 23 int lowbit(int x) 24 { 25 return x&(-x); 26 } 27 28 void update(int x,int y) 29 { 30 while(x < N) 31 bit[x] += y,x += lowbit(x); 32 } 33 34 int Query(int x) 35 { 36 int ret = 0; 37 while(x) 38 ret += bit[x],x -= lowbit(x); 39 return ret; 40 } 41 42 int Query(int l,int r) 43 { 44 return Query(r)-Query(l-1); 45 } 46 47 int main() 48 { 49 while(scanf("%d %d",&n,&q) != EOF) 50 { 51 clr(bit,0); clr(vis,0); clr(nxt,0); clr(last,-1); 52 // 输入的同时在后边复制一份 53 for(int i = 1;i <= n;i++) 54 { 55 scanf("%d",&a[i]); 56 a[i+n] = a[i]; 57 } 58 n *= 2; 59 60 // 我认为这个循环是最神奇的地方 61 // 很强大 一个月前看的时候根本没怎么看懂 62 pre[0] = 0; 63 for(int i = 1;i <= n;i++) 64 { 65 // 处理前缀和 66 if(!vis[a[i]]) // 如果没出现过 67 { 68 vis[a[i]] = true; 69 pre[i] = pre[i-1]+1; // 类似dp 70 } 71 else // 如果已经出现过 72 { 73 pre[i] = pre[i-1]; 74 } 75 // 处理nxt[]数组 last[]数组做辅助用 76 // a[i]首次出现时不会执行此语句 【 ~(-1) = 0 】 77 if(~last[a[i]]) 78 { 79 nxt[last[a[i]]] = i; 80 } 81 last[a[i]] = i; 82 } 83 84 for(int i = 0;i < q;i++) 85 { 86 // 因为原来的代码增加query[i].l的值后又交换 87 // 了query[i].l与query[i].r的值 88 // 我直接在输入时交换一下l与r的值 然后增加r的值 89 // 最终的结果是一样的 90 scanf("%d %d",&query[i].r,&query[i].l); 91 query[i].r += n/2; 92 // 因为在输入完成后会对query[]保存到值按照左端点重新排序 93 // 因此需要用query[i].id来记录一下原来的顺序,即i的值 94 query[i].id = i; 95 } 96 // 按照左端点对query[]进行排序 97 sort(query,query+q); 98 // 从最左端的1开始向右扫描 99 // 每次右移的时候把对应的数的下一个位置加入到数状数组中 100 int now = 1; 101 for(int i = 0;i < q;i++) 102 { 103 while(now < query[i].l) 104 { 105 if(~nxt[now]) 106 { 107 update(nxt[now],1); 108 } 109 ++now; 110 } 111 ans[query[i].id] = pre[query[i].r]-pre[query[i].l-1]+Query(query[i].l,query[i].r); 112 } 113 for(int i = 0;i < q;i++) 114 printf("%d\n",ans[i]); 115 } 116 return 0; 117 }