题意
给定一个序列,长度为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)
分析
1 wzy学长选拔赛出的题,我发现超经典,奈何我弱鸡没写出来,看题解半天才明白
2 本题的关键就在于离线, 一次更新之后进行了m次查询,故称为离线查询,于是你可以将查询数组排序然后按右端点的前后求结果
3 用树状数组求解这种区间查询,单点更新(每次加入一个点),区间查询
4 本题还是重点在于自己的理解,注释也不好写
2 具体分析参见代码注释
参考代码
//......................................................离线树状数组
int n,m;
const int LEN = 2e5+100;
int tree[LEN];//树状数组
int ans[LEN];//答案数组
int ar[LEN];
int last[LEN];//last[i]上一个与ar[i]相等的元素的位置
map<int,int> ma;//存储每一个数对应的最后的位置
struct Q
{
int l,r,ID;
};
Q q[LEN];
bool operator <(const Q &a,const Q &b)
{
return a.r < b.r;
}
void modify(int x,int d)
{
while(x <= n)
{
tree[x] += d;
x += lowbit(x);
}
}
int Query(int x)
{
int sum = 0;
while(x>0)
{
sum += tree[x];
x -= lowbit(x);
}
return sum;
}
int main()
{
cin>>n>>m;
for(int i = 1; i <= n; ++i)
{
scanf("%d",&ar[i]);
last[i] = ma[ar[i]];
ma[ar[i]] = i;
}
for(int i = 1; i <= m; ++i)
{
scanf("%d %d",&q[i].l,&q[i].r);
q[i].ID = i;
}
sort(q+1,q+m+1);
int index = 1;
/*树状数组的目的是进行快速求和, 我们可以假设求和的数组是C*/
for(int i = 1; i <= n; ++i)
{
if(last[i]!=0)
modify(last[i],1);//将上一个与这个元素相同的元素的位置+1,代表有一组
int p = last[last[i]];
if(p != 0)
{
modify(p,-2);/*如果有三个或者多个该元素,则需要-2,把+1抵消,并且把之前 p 和 last[i] 这个组合抵消 int pp = last[p]; if(pp != 0)//消除-2的影响 modify(pp,1); } / 分析后得知C[i]只有三种可能的值,0,-1,1,
*/
while(index <= m&&q[index].r == i)
{
ans[q[index].ID] = Query(i) - Query(q[index].l-1);/*这个时候Query(i)就代表从1到i有多少个恰好两次的不同数,Query(q[index].l-1)则不是*/
index ++;
}
}
for(int i = 1; i <= m; ++i)
printf("%d\n",ans[i]);
return 0;
}