题目链接: Bzoj1878.
—————————————-
概述
题目大意如下.
给定一个长度为
n
的序列
(1≤n≤50000,1≤m≤200000,1≤ai≤1000000)
—————————————-
分析
听说这是树状数组模板题? 那我还是太弱了..看了好久才发现可以用树状数组.
我们观察一下, 一次询问的答案是这段区间内数字的种类数, 但是有些数会重复出现许多次, 那么我们能不能让每一种数只提供一次贡献呢?
答案是可以的. 我们不妨先考虑暴力一点的做法, 对于每一个询问, 我们在区间里从左往右扫一遍, 假如当前这个数以前没有出现过, 就把这个位置的贡献加1, 假如以前出现过, 就把上一次出现的位置贡献减1, 再把当前位置的贡献加1, 这样就可以保证每一种数只会产生1的贡献. 最后统计这个区间内的贡献之和就行了.
乍一看这个做法似乎是毫无意义的, 因为你都已经从左往右扫了一遍了, 还那么麻烦的加1减1干什么? 直接用桶暴力去统计答案不是方便多了吗?
肤浅! 没看到上面是在单点修改区间查询嘛?
迂腐! 不知道有个套路叫做区间排序嘛?
咳咳, 我们按询问区间的右端点排序然后依次处理排序后的区间, 查询完第
i
个区间之后, 将第
那么就这么愉快地分析完了这道模板题啦. 时间复杂度 O((n+m)logn).
—————————————-
代码
#include<bits/stdc++.h>
#define For(i, j, k) for(int i = j; i <= (int)k; ++ i)
using namespace std;
const int maxn = 50000 + 5;
const int maxm = 200000 + 5;
int n, m, tot;
int a[maxn], pre[maxn], c[maxn], Ans[maxm];
struct Que{
int l, r, id;
bool operator < (Que o)const{
return r < o.r;
}
}q[maxm];
map<int,int> mp;
inline int Read(){
int x = 0; char c = getchar();
for(; !isdigit(c); c = getchar());
for(; isdigit(c); c = getchar()) x = (x << 3 ) + (x << 1) + (c ^ 48);
return x;
}
inline void add(int x, int v){
for(; x < maxn; x += x & (-x))
c[x] += v;
}
inline int sum(int x){
int back = 0;
for(; x; x -= x & (-x))
back += c[x];
return back;
}
int main(){
file();
n = Read();
For(i, 1, n){
a[i] = Read();
mp[a[i]];
}
m = Read();
For(i, 1, m){
q[i].id = i;
q[i].l = Read(); q[i].r = Read();
}
for(map<int,int>::iterator p = mp.begin(); p != mp.end(); ++ p)
p->second = ++ tot;//这是一个优秀的离散化方法, 可以自己理解一下.
For(i, 1, n) a[i] = mp[a[i]];
sort(q + 1, q + 1 + m);
int now = 1, l, r;
For(i, 1, m){
l = q[i].l; r = q[i].r;
while(now <= r){
if(pre[a[now]]) add(pre[a[now]], -1);
add(pre[a[now]] = now, 1);
++ now;
}
Ans[q[i].id] = sum(r) - sum(l - 1);
}
For(i, 1, m)
printf("%d\n", Ans[i]);
return 0;
}
—————————————-
小结
这道模板题困扰了我还挺久的, 开始在想普通线段树, 后来发现维护不了, 于是想权值线段树, 结果发现理解错了题意… 后来冥思苦想半天才发现了我上面说的那个暴力方法, 用树状数组优化一下就可以了. 果然还是套路题做少了, 还要继续努力.
—————————————-
——wrote by miraclejzd.