题目描述
Given a sequence of integers a1, a2, ..., an and q pairs of integers (l1, r1), (l2, r2), ..., (lq, rq), find count(l1, r1), count(l2, r2), ..., count(lq, rq) where count(i, j) is the number of different integers among a1, a2, ..., ai, aj, aj + 1, ..., 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 n and q.
The second line contains n integers a1, a2, ..., an.
The i-th of the following q lines contains two integers li and ri.
输出描述:
For each test case, print q integers which denote the result.
示例1
输入
复制
3 2
1 2 1
1 2
1 3
4 1
1 2 3 4
1 3
输出
复制
2
1
3
备注:
* 1 ≤ n, q ≤ 105
* 1 ≤ ai ≤ n
* 1 ≤ li, ri ≤ n
* The number of test cases does not exceed 10.
题意:给你一个数组长度为n,有q次查询,每次查询,给你一个区间(L,R),让你求[1,L] + [R,n] 这个中有多少不同的数字;
当时做这道题时,想着是莫队,感觉不稳,队友说用 树状数组 +离线能写
思路: 因为询问的是 [1,L] + [R,n]中不同数字的个数,我们先用倍增法,把长度为n的数组,扩增到 2*n,问[1,L] + [R,n]之间不同数的个数,其实就是问[R,n+L] 中不同数的个数;
把询问区间按右端点从小到大排序,离线操作,从1 操作到 shu[i].r,操作过程中,记录a[j]上一次出现的位置,当操作这个位置时,这个位置的值是a[j],把这个位置上 +1,把上一次出现 a[j]的位置 -1,操作到当前位置时,在1~当前位置这个区间中,保证每个数计数都是在最后一次出现的位置上。
个人感觉:当离线操作时,按左端点排序时,一般都是树状数组先从前往后初始化了一边了,再从1~shu[i].l消除影响,当按右端点排序时,一般树状数组中没有从先往后初始化。请注意我的措辞,一般情况下,但还有具体情况具体分析;
代码:
#include<bits/stdc++.h>
using namespace std;
#define Max 2*100005
int a[Max]; // 求[1,L]+[R+n]中不同数的数量时,把所给的区间倍增;
//就是求[R,L+n]中的不同数的数量;
int c[Max];
int pre[Max]; // a[j], pre[a[j]],表示这个前一个a[j]出现的位置;
int anss[Max];
int n,q;
struct node
{
int pi;
int l,r;
}stu[Max];
int cmp(node a,node b)
{
return a.r<b.r;
}
int lowbit(int x)
{
return x&-x;
}
void add(int x,int k)
{
while(x<=n)
{
c[x] += k;
x += lowbit(x);
}
}
int sum(int x)
{
int ans = 0;
while(x>0)
{
ans += c[x];
x -= lowbit(x);
}
return ans;
}
int main()
{
while(~scanf("%d%d",&n,&q))
{
memset(c,0,sizeof(c));
memset(pre,0,sizeof(pre));
for(int i = 1;i<=n;i++) // 扩增;
{
scanf("%d",&a[i]);
a[i+n] = a[i];
}
int l,r;
for(int i = 1;i<=q;i++)
{
scanf("%d%d",&l,&r);
stu[i].l = r;
stu[i].r = l+n;
stu[i].pi = i;
}
n<<=1;
sort(stu+1,stu+q+1,cmp);
int j = 1;
for(int i = 1;i<=q;i++)
{
for(;j<=stu[i].r;j++)
{
if(pre[a[j]]) add(pre[a[j]],-1); // 说明a[j]这个数前面已经出现过,把上一次出现a[j]位置上的记录消除;
add(j,1); // 把当前位置上,记录;
pre[a[j]] = j; //记录上一个a[j] 出现的位置;
}
anss[stu[i].pi] = sum(stu[i].r) - sum(stu[i].l-1);
// 当从1 操作到 shu[i].r时,数状数组中存时,[1,shu[i].r]中不同数的个数;
//而sum(stu[i].l-1) ,存的是[stu[i].l,stu[i].r] 中没有出现的数 的不同数的个数;
}
for(int i = 1;i<=q;i++)
printf("%d\n",anss[i]);
}
return 0;
}