SPOJ DQUERY - D-query (莫队算法|主席树|离线树状数组)

时间:2023-03-09 08:32:31
SPOJ  DQUERY - D-query (莫队算法|主席树|离线树状数组)

DQUERY - D-query

Given a sequence of n numbers a1, a2, ..., an and a number of d-queries. A d-query is a pair (i, j) (1 ≤ i ≤ j ≤ n). For each d-query (i, j), you have to return the number of distinct elements in the subsequence ai, ai+1, ..., aj.

Input

  • Line 1: n (1 ≤ n ≤ 30000).
  • Line 2: n numbers a1, a2, ..., an (1 ≤ ai ≤ 106).
  • Line 3: q (1 ≤ q ≤ 200000), the number of d-queries.
  • In the next q lines, each line contains 2 numbers i, j representing a d-query (1 ≤ i ≤ j ≤ n).

Output

  • For each d-query (i, j), print the number of distinct elements in the subsequence ai, ai+1, ..., aj in a single line.

Example

Input
5
1 1 2 1 3
3
1 5
2 4
3 5 Output
3
2
3 题意:给你一段区间,q个询问,询问区间[l,r]有多少个不同的数字
思路: 这道题算是很有意思的一道数据结构题了。。解法很多:莫队,主席树,离线树状数组都可以。解法也不难。 莫队解法:
思路:这道题感觉就是直接莫队莽上就完事了,没有什么需要处理的,就维护下每个元素在当前区间出现的次数就好了。
实现代码:
#include<bits/stdc++.h>
using namespace std; const int M = 1e6 + ;
int a[M],num[M],vis[M],block,ans = ,n,m;
struct node{
int l,r,id;
bool operator < (const node &cmp) const{
if(l/block == cmp.l/block) return r < cmp.r;
return l/block < cmp.l/block;
}
}q[M]; void add(int x){
vis[a[x]]++;
if(vis[a[x]] == ) ans++;
} void del(int x){
vis[a[x]]--;
if(vis[a[x]] == ) ans--;
} int main()
{
scanf("%d",&n);
block = sqrt(n);
for(int i = ;i <= n;i ++){
scanf("%d",&a[i]);
}
scanf("%d",&m);
for(int i = ;i <= m;i ++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id = i;
}
sort(q+,q+m+);
int l = ,r = ;
for(int i = ;i <= m;i ++){
while(l < q[i].l) del(l),l++;
while(l > q[i].l) l--,add(l);
while(r < q[i].r) r++,add(r);
while(r > q[i].r) del(r),r--;
num[q[i].id] = ans;
}
for(int i = ;i <= m;i ++)
printf("%d\n",num[i]);
return ; }

ps:

使用莫队算法耗时为:270ms;

主席树解法:

我们将元素在序列中的地址建成主席树,如果这个元素没有出现过,那么就在这个位置上+1,如果这个元素出现过那就将这个元素上一次出现的位置-1,在当前位置+1;每棵线段树维护的都是1到当前

位置的不同元素的个数。

实现代码:

#include<bits/stdc++.h>
using namespace std; const int M = 3e4+;
const int Max = 1e6+;
int rs[M*],ls[M*],sum[M*],root[M*];
int a[M],idx;
int vis[Max],ret; void update(int old,int &k,int l,int r,int p,int c){
k = ++idx;
ls[k] = ls[old]; rs[k] = rs[old]; sum[k] = sum[old] + c;
if(l == r) return ;
int mid = (l + r) >> ;
if(p <= mid) update(ls[old],ls[k],l,mid,p,c);
else update(rs[old],rs[k],mid+,r,p,c);
} void query(int k,int l,int r,int p){
if(p == l){
ret += sum[k];
return ;
}
int mid = (l + r) >> ;
if(p <= mid) ret += sum[rs[k]],query(ls[k],l,mid,p);
else query(rs[k],mid+,r,p);
return ;
} int main()
{
int n,q;
scanf("%d",&n);
idx = ;
for(int i = ;i <= n;i ++) scanf("%d", &a[i]);
memset(vis,,sizeof(vis));
for(int i = ;i <= n;i ++){
int tmp = ;
if(!vis[a[i]]) update(root[i-],root[i],,n,i,);
else {
update(root[i-],tmp,,n,vis[a[i]],-);
update(tmp,root[i],,n,i,);
}
vis[a[i]] = i;
}
scanf("%d",&q);
while(q--){
int l,r;
scanf("%d %d",&l,&r);
ret = ;
query(root[r],,n,l);
printf("%d\n",ret);
}
return ;
}

ps:

主席树耗时为:200ms;

离线+树状数组

先离线下,对询问的r排序,以元素的下标作树状数组维护以r为右边界的区间不同元素的数量,遍历时如果当前元素没有出现,那么存在他的地址,并在树状数组对应下标+1,如果这个元素

之前已经出现过了,那么取消之前标记的点也就是将这个元素上一次出现的下标在树状数组中-1,变成0,然后再储存下当前元素最迟出现的下标,也就是当前点。

最后区间[l,r]之间不同的数量也就是sumR - sumL;

实现代码:

#include<bits/stdc++.h>
using namespace std; const int Max = 1e6+;
int c[Max],a[Max],n,m,ans[Max],mp[Max];
int lowbit(int x){
return x&(-x);
} int getsum(int x){
int sum = ;
while(x>){
sum += c[x];
x -= lowbit(x);
}
return sum;
} void add(int x,int value){
while(x<=n){
c[x] += value;
x += lowbit(x);
}
} struct node{
int l,r,id;
bool operator < (const node &cmp) const {
return r < cmp.r;
}
}q[Max]; int main()
{
scanf("%d",&n);
memset(mp,-,sizeof(mp));
for(int i = ; i <= n;i ++) scanf("%d",&a[i]);
scanf("%d",&m);
for(int i = ;i <= m;i ++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id = i;
}
sort(q+,q+m+);
int l = ;
for(int i = ;i <= m;i ++){
for(int j = l;j <= q[i].r;j ++){
if(mp[a[j]]!=-){
add(mp[a[j]],-);
}
add(j,);
mp[a[j]] = j;
}
l = q[i].r + ;
ans[q[i].id] = getsum(q[i].r) - getsum(q[i].l-);
} for(int i = ;i <= m;i ++){
printf("%d\n",ans[i]);
}
return ;
}

ps:

离线+树状数组耗时为:150ms

很明显这道题使用离线+树状数组的耗时比主席树和莫队要少很多。。且代码量也比较少;