链接:https://ac.nowcoder.com/acm/contest/19684/B
[SDOI2009]HH的项链
HH有一串由各种漂亮的贝壳组成的项链。
HH相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一 段贝壳,思考它们所表达的含义。
HH不断地收集新的贝壳,因此他的项链变得越来越长。
有一天,他突然提出了一 个问题:某一段贝壳中,包含了多少种不同的贝壳?
这个问题很难回答。。。因为项链实在是太长了。于是,他只 好求助睿智的你,来解决这个问题。
输入描述:
第一行:一个整数N,表示项链的长度。
第二行:N个整数,表示依次表示项链中贝壳的编号(编号为0到1000000之间的整数)。
第三行:一个整数M,表示HH询问的个数。
接下来M行:每行两个整数,L和R(1 ≤ L ≤ R ≤ N),表示询问的区间。
N ≤ 50000,M ≤ 200000。
输出描述:
M行,每行一个整数,依次表示询问对应的答案。
示例1
输入
复制
6
1 2 3 4 3 5
3
1 2
3 5
2 6
输出
复制
2
2
4
备注:
对于20%的数据,1\le n,m\leq 50001≤n,m≤5000
对于40%的数据,1\le n,m\leq 10^51≤n,m≤10 5
对于60%的数据,1 \le n,m\leq 5\times 10^51≤n,m≤5×105
对于100%的数据,1\le n,m,a_i \leq 10^6,1\le l \le r \le n1≤n,m,a i≤10 6 ,1≤l≤r≤n。
本题可能需要较快的读入方式,最大数据点读入数据约 20MB
本题是一道莫队算法的题目,但是同时可以使用树状数组,线段树或者主席树来解决
由于本人莫队算法不精,就写一下离线的树状数组写法和在线的主席树:
树状数组
大致思路就是
1.先读入所有数据并按照区间右端点排序
2.从小到大遍历右端点,对于每次固定的右端点,我们按照左端点来依次查询
3.查询方式:记录每一个值在此之前出现的位置,当遍历到某一位置时,将其上一个位置处的值-1,此处值+1,这样从开头到当前点的前缀和即为种类个数。
如果仍然不理解可以看图:
AC代码小贴士:空间一定要开到大于1e6
#include<bits/stdc++.h>
using namespace std;
#define N 50010
#define M 1000010
struct Ques{
int id;
int l,r;
bool operator < (const Ques W) const{
return r<W.r;
}
}ques[M];
int n,m;
int w[M],last[M];
int ans[M];
int tr[M];
int lowbit(int x){
return x&-x;
}
void add(int u,int x){
for(int i=u;i<=n;i+=lowbit(i)){
tr[i]+=x;
}
}
int ask(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
//build();
scanf("%d",&m);
for(int i=1;i<=m;i++){
int l,r;
scanf("%d%d",&ques[i].l,&ques[i].r);
ques[i].id=i;
}
sort(ques+1,ques+1+m);
for(int i=1,r=0;i<=m;i++){
while(r<ques[i].r){
r++;
if(last[w[r]])
add(last[w[r]],-1);
add(r,1);
last[w[r]]=r;
}
ans[ques[i].id]=ask(r)-ask(ques[i].l-1);
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}
看到区间查询就想到可持久化线段树,然后就可以尝试用主席树来做一下了
其实主席树的思路不是很好想,我是借鉴了这位博主的思路:
https://blog.csdn.net/weixin_45799835/article/details/116649809
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 1; i <= (n); ++i)
using namespace std;
typedef long long ll;
const int N = 5E4 + 10, M = 2E5 + 10, QAQ = 1E6 + 10, L = 0, R = N - 5; // 测的题不同, 所以M变为了2E5.
int last[QAQ];
struct node {
int l, r;
int val;
}t[N << 6];
int root[N], ind;
int build(int a, int c, int tl, int tr, int p) {
int x = ++ind; t[x] = t[p];
t[x].val += c;
if (tl == tr) return x;
int mid = tl + tr >> 1;
if (a <= mid) t[x].l = build(a, c, tl, mid, t[p].l);
else t[x].r = build(a, c, mid + 1, tr, t[p].r);
return x;
}
int ask(int l, int r, int tl, int tr, int p, int x) {
if (l <= tl and r >= tr) return t[x].val - t[p].val;
int mid = tl + tr >> 1;
int res = 0;
if (l <= mid) res += ask(l, r, tl, mid, t[p].l, t[x].l);
if (r > mid) res += ask(l, r, mid + 1, tr, t[p].r, t[x].r);
return res;
}
int main()
{
int n; cin >> n;
rep(i, n) {
int col; scanf("%d", &col);
root[i] = build(last[col], 1, L, R, root[i - 1]);
last[col] = i;
}
int m; cin >> m;
rep(i, m) {
int l, r; scanf("%d %d", &l, &r);
printf("%d\n", ask(0, l - 1, L, R, root[l - 1], root[r]));
}
return 0;
}
扩展:
https://ac.nowcoder.com/acm/contest/19684/C
当扩展至两个的时候,只需要将last数组开成二维即可,很像一些dp还有次小生成树的意思。
#include<bits/stdc++.h>
using namespace std;
#define N 50010
#define M 1000010
struct Ques{
int id;
int l,r;
bool operator < (const Ques W) const{
return r<W.r;
}
}ques[M];
int n,m,c;
int w[M],last[M][2];
int ans[M];
int tr[M];
int lowbit(int x){
return x&-x;
}
void add(int u,int x){
for(int i=u;i<=n;i+=lowbit(i)){
tr[i]+=x;
}
}
int ask(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=tr[i];
return res;
}
int main(){
scanf("%d%d%d",&n,&c,&m);
for(int i=1;i<=n;i++)
scanf("%d",&w[i]);
//build();
for(int i=1;i<=m;i++){
int l,r;
scanf("%d%d",&ques[i].l,&ques[i].r);
ques[i].id=i;
}
sort(ques+1,ques+1+m);
for(int i=1,r=0;i<=m;i++){
while(r<ques[i].r){
r++;
if(last[w[r]][1]){
add(last[w[r]][1],-1);
add(last[w[r]][0],1);
}
else if(last[w[r]][0])
add(last[w[r]][0],1);
last[w[r]][1]=last[w[r]][0];
last[w[r]][0]=r;
}
ans[ques[i].id]=ask(r)-ask(ques[i].l-1);
}
for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
return 0;
}