1.离线处理的含义是将查询全部处理之后在输出结果。我们用到排序来简化操作,使得我们不能边处理边输出答案。
2.要熟知树状数组的更新,查询,lowbit等基本操作。
洛谷1972
现附上代码
#include<stdio.h>
#include<algorithm>
using namespace std;
const int N = 1e6 + 5;
int v[N], t[N], ans[N], a[N];
struct query
{
int l, r;
int pos;
} q[N];
bool cmp(query x, query y)
{
return x.r < y.r;
}
int lowbit(int x)
{
return x & (-x);
}
void update(int x, int d)
{
while (x <= N)
{
t[x] += d;
x += lowbit(x);
}
}
int sum(int x)
{
int ans = 0;
while (x > 0)
{
ans += t[x];
x -= lowbit(x);
}
return ans;
}
int main()
{
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
int m;
scanf("%d", &m);
for (int i = 1; i <= m; i++)
{
scanf("%d %d", &q[i].l, &q[i].r);
q[i].pos = i;
}
sort(q + 1, q + m + 1, cmp);
int nex = 1;
for (int i = 1; i <= m; i++)
{
for (int j = nex; j <= q[i].r; j++)
{
if (v[a[j]])
{
update(v[a[j]], -1);
}
update(j, 1);
v[a[j]] = j;
}
nex = q[i].r + 1;
ans[q[i].pos] = sum(q[i].r) - sum(q[i].l - 1);
}
for (int i = 1; i <= m; i++)
printf("%d\n", ans[i]);
return 0;
}
首先要注意理解一下以下几个关键点
1.v数组用来记录一种贝壳在之前有没有出现过,并记录最后一次出现的下标,a数组用来存放贝壳的种类,ans数组用来存放每个查询的结果,t数组是树状数组,用来存放当前需要考虑的贝壳。
2.显然,我们只需要考虑相同出现的贝壳中最右边的那一个就行了。当我们每处理完一个查询时, 得到的t数组可以通过求前缀和的方式得到这个查询的答案。
举个例子
用样例来说明,n = 6, 1, 2, 3, 4, 3, 5
考虑特殊情况区间[3, 5], 当考察a[3] = 3时,可以发现之前并没有出现过3,因此是有效的,所有的贝壳的有效值为0, 0 ,1, 0, 0,而很关键的,树状数组可以很快的求出该数组的前缀和,它的前缀和就是区间查询的答案。在考察a[4] = 4, 所有贝壳的有效值为0, 0, 1, 1, 0, 0;在考察a[5] = 3, 在下标为3的位置出现过了,因此在这个区间里,a[3] = 3变成了无效值,a[5] = 3是有效值,所有贝壳的有效值为0, 0, 0, 1, 1, 0,得到sum(5) - sum(2) = 2;
还要注意的是每处理一个查询,左边在查询区间外的贝壳如果与查询区间内有相同的是会变为无效的,因此得处理完一个查询就要保存一个查询的答案。
举个例子
和上面一样的先处理[1, 3]区间,得到1,1,1, 0, 0,0,在处理[5, 6]区间,得到1,1,0,1,1,1,现在再去得到[1,3]区间的结果为2, 但这是错误的因此每处理完一个区间就应该保存一个答案。
如有不对的地方,欢迎指出。