题目链接
题目大意
给你一个长度为(n(nle30000))的数组(a)
给你(m(mle100000))次查询,求出区间([l,r])中出现过的数字之和(出现过多次只算一次)
题目思路
这个乍一看很像线段树,其实也是线段树
但是你会发现你根本不好去维护这个线段树
你发现没这个数组没有修改操作,都是查询操作
那么对于这种题目就应该要想到离线操作
把所有查询按照(r)升序排序
然后保证([1,r])中若出现相同的数,则这个数只在([1,r])的最右边记录
那么这样显然可以保证答案的正确性,只要有一个(last)数组维护每个元素前的相同元素的最近值即可
由于(a[i])达到(10^9)用(map)即可
代码
#include<bits/stdc++.h>
#define fi first
#define se second
#define debug cout<<"I AM HERE"<<endl;
using namespace std;
typedef long long ll;
const int maxn=1e5+5,inf=0x3f3f3f3f,mod=1e9+7;
const double eps=1e-6;
int n,m;
int a[maxn];
ll ans[maxn];
ll tree[maxn<<2];
struct node{
int l,r,id;
}nd[maxn];
bool cmp(node a,node b){
return a.r<b.r;
}
ll query(int node,int l,int r,int ql,int qr){
//这里没有被建树,显然没有值
if(ql<=l&&r<=qr){
return tree[node];
}
int mid=(l+r)/2;
ll sum=0;
if(mid>=ql) sum+=query(node<<1,l,mid,ql,qr);
if(mid<qr) sum+=query(node<<1|1,mid+1,r,ql,qr);
return sum;
}
void update(int node,int l,int r,int pos,int val){
if(l==r){
tree[node]=val;
return ;
}
int mid=(l+r)/2;
if(mid>=pos) update(node<<1,l,mid,pos,val);
else update(node<<1|1,mid+1,r,pos,val);
tree[node]=tree[node<<1]+tree[node<<1|1];
}
signed main(){
int _;scanf("%d",&_);
while(_--){
map<int,int>last;
memset(tree,0,sizeof(tree));
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&nd[i].l,&nd[i].r);
nd[i].id=i;
}
sort(nd+1,nd+1+m,cmp);
int pos=1;
for(int i=1;i<=m;i++){
while(pos<=nd[i].r){
if(last[a[pos]]){
update(1,1,n,last[a[pos]],0);
}
update(1,1,n,pos,a[pos]);
last[a[pos]]=pos;
pos++;
}
ans[nd[i].id]=query(1,1,n,nd[i].l,nd[i].r);
}
for(int i=1;i<=m;i++){
printf("%lld
",ans[i]);
}
}
return 0;
}