Given a sequence of integers a1, a2, ..., an and q pairs of integers (l 1, 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 a 1, 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 a 1, a2, ..., an.The i-th of the following q lines contains two integers l i and ri.输出描述:For each test case, print q integers which denote the result.备注
-
1 ≤ n, q ≤ 1e5
-
1 ≤ ai ≤ n
-
1 ≤ li, ri ≤ n
-
The number of test cases does not exceed 10.示例1:输入3 21 2 11 21 34 11 2 3 41 3输出2 1 3
题目大意:
给一个1到N个数,Q次查询。每次查询L,R,返回1到L,R到N两个区间内不同的数字的数量。
想法:
1e5次查询,这个for循环要有的,用set暴力找不同的话,又来1e5个数循环,n^2复杂度铁定超时。可以知道一次查询至多logn。区间查询啊,容易想到树状数组。不过不知道树状数组的节点该存什么(一个节点一个set好了,笑)。这是第一个有很多人做出来的,但感觉不是签到题,卡了很久然后去做别的了。感谢曦哥挽回了爆零的局面。以下解题思路由曦哥提供
思路:
1、查询区间内不同的数,就是区间内有哪些数是第一次出现的(相对于这些数后面)。它们的数量即查询结果。
2、树状数组查找区间不同数的板子。 节点存原数组区间内有几个数字是第一次出现的。
3、把两个查询区间合并成一个,简化问题。就是把数组加倍,首尾相连。那么一次LR查询区间就是,R到N,N到N+L的一个区间。
4、离线查询。将查询按l排序,这样区间就是一直右移的,就可以只刷新要用的右半部分树状数组。不然每次从头刷新(而且这个做不到的吧)。
过程:
离线存查询q[],合并区间,l=R,r=N+L。q[]按r降序排。从q[]中最大r开始刷一遍next[]。(next[i]:i位置上数字在后面再次出现的位置。用于更新first数组)也刷一遍first[] 。(first[i]:i位置上的数字是不是在区间内第一次出现。是树状数组的原数组)。根据first[]初始化树状数组(区间节点存该区间内first为true的个数,即查询结果)。
遍历q[],l、r传入树状数组得结果。在每次次遍历前,因为l右移,l前的first[]要更新到l后,fir[next[i]]=true,同时更新树状数组。
AC代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
int N,M;
const int SIZE = 250005;
int c[SIZE];
int A[SIZE];
int Next[SIZE];
int res[200005];
int show[2000005];
bool fir[SIZE];
struct Q
{
int l,r;
int pos;
}q[200005];
int lowbit(int k)
{
return k&(-k);
}
void modify(int n,int v)
{
while(n <= N)
{
c[n] += v;
n += lowbit(n);
}
}
int sum(int n)
{
int ans = 0;
while(n > 0)
{
ans += c[n];
n -= lowbit(n);
}
return ans;
}
int cmp(Q a, Q b)
{
return a.l < b.l;
}
int main()
{
while(~scanf("%d",&N)){
memset(res,0,sizeof(res));
memset(c,0,sizeof(c));
memset(A,0,sizeof(A));
memset(Next,0,sizeof(Next));
memset(fir,0,sizeof(fir));
memset(show,0,sizeof(show));
scanf("%d",&M);
for(int i=1;i<=N;i++) scanf("%d",&A[i]);//A原数组
for(int i=1;i<=N;i++) A[N+i]=A[i];//A原数组
N=N*2;
for(int i=N;i>=1;i--)
{
if(!show[A[i]])//show没出现过
{
show[A[i]] = i;
fir[i] = true;//当前位置的数是第一次出现
}
else
{//show出现过
Next[i] = show[A[i]];//记录他下一次出现的位置
fir[Next[i]] = false;//下一个位置不是第一次出现
fir[i] = true;//这个位置是第一次出现
show[A[i]] = i;//记录出现位置
}
}
for(int i=1;i<=M;i++)
{
int l,r;
scanf("%d%d",&l,&r);
q[i].l=r;
q[i].r=l+N/2;
// cout<<q[i].l<<q[i].r<<endl;
q[i].pos = i;
}
sort(q+1,q+1+M,cmp);
for(int i=1;i<=N;i++)
if(fir[i])//如果这个数是第一次出现
{
modify(i,1);//初始化
}
int qtemp = q[1].l;//当前位置
int ptr = 1;//下标位置
for(int i=1;i<=M;i++)
{
for(;ptr<q[i].l;ptr++)
{
if(fir[ptr])//如果第一次出现
{
modify(ptr,-1);
fir[ptr] = false;//下标在l之前不在区间内
if(Next[ptr])//如果后面还有重复
{
fir[Next[ptr]] = true;//下一个是第一次出现
modify(Next[ptr],1);//这个位置的前一个位置次数
}
}
}
ptr = q[i].l;
qtemp = q[i].l;
res[q[i].pos] = sum(q[i].r) - sum(q[i].l-1);
}
for(int i=1;i<=M;i++) printf("%d\n",res[i]);
}
return 0;
}