题意:
给一个长度为n的序列a,q组询问,每组询问给出L和R,问[L,R]中出现过的数字之和(出现过多次的仅计算一次)
数据范围:n<=5e4,a(i)<=1e9,q<=1e5
解法:
线段树很难直接维护,因为无法统计每一个区间节点中有哪几种数,很难快速将子节点的信息合并
用last[i]表示数a[i]上一次出现的位置
对于一组询问[l,r],如果last[i]<l,那么说明这个a[i]在[l,r]中第一次出现,对答案有贡献
如果last[i]>=l,说明这个a[i]已经在区间内出现过了,对答案无贡献,不计入答案
那么可以把问题转化为,对于每组询问,计算[l,r]中last[i]<l的a[i]的和
可以离线求解,对last和query排序,顺序枚举询问query,把满足last[i]<l的所有a[i]加入线段树的位置i
然后当前询问的答案就是线段树内[l,r]的和,因为线段树中[l,r]的数的last一定小于当前询问的l
代码用的树状数组,因为码量小
code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e5+5;
struct BIT{
int c[maxm];
int lowbit(int i){return i&-i;}
void init(){memset(c,0,sizeof c);}
void add(int i,int t){while(i<maxm)c[i]+=t,i+=lowbit(i);}
int ask(int i){int ans=0;while(i)ans+=c[i],i-=lowbit(i);return ans;}
}bit;
struct Q{
int l,r,id;
bool operator<(Q a){
return l<a.l;
}
}query[maxm];
struct L{
int l,id,v;
bool operator<(L a){
return l<a.l;
}
}last[maxm];
int mark[maxm];
int ans[maxm];
int xx[maxm];
int a[maxm];
int n,q;
signed main(){
int T;cin>>T;
while(T--){
int n;cin>>n;
for(int i=1;i<=n;i++)cin>>a[i],xx[i]=a[i];
sort(xx+1,xx+1+n);
int num=unique(xx+1,xx+1+n)-xx-1;
for(int i=1;i<=num;i++)mark[i]=0;
for(int i=1;i<=n;i++){
a[i]=lower_bound(xx+1,xx+1+num,a[i])-xx;
last[i]={mark[a[i]],i,xx[a[i]]};
mark[a[i]]=i;
}
sort(last+1,last+1+n);
//
int q;cin>>q;
for(int i=1;i<=q;i++){
cin>>query[i].l>>query[i].r;
query[i].id=i;
}
sort(query+1,query+1+q);
//
bit.init();
int k=1;
for(int i=1;i<=q;i++){
int l=query[i].l,r=query[i].r;
while(k<=n&&last[k].l<l){
bit.add(last[k].id,last[k].v);
k++;
}
ans[query[i].id]=bit.ask(r)-bit.ask(l-1);
}
for(int i=1;i<=q;i++)cout<<ans[i]<<endl;
}
return 0;
}