题意
给定一个长度为 \(n\) 的序列,\(m\) 个查询,每次查询区间 \([L,R]\) 范围内不同元素的和。
\(1\leq T \leq 10\)
\(1 \leq n\leq 30000\)
\(1\leq m\leq 100000\)
思路
这道题没有强制在线,又没有修改,离线会比在线好想。
可以从第 \(1\) 个数到第 \(n\) 个数一次添加,并去除之前的相同元素,以此为顺序。就是说对于 \(m\) 个询问,按右端点进行排序,以此添加进每个数字并只保留最右端的数,借助 \(\text{map}\) 去重,区间和用树状数组维护。
假如要强制在线,该怎么办呢?
假如我们能力开下 \(n\) 个树状数组,就可以在线的查询了,可是 \(n\) 个树状数组肯定开不下,那动态开点线段树?可以,但是每次也是要把原来的线段树复制一遍,复杂度一累,时间过不去。
主席树的作用就体现出来了,回顾离线的写法,每次只会修改 \(1-2\) 个位置,那在前缀的基础上,保留原来的历史版本不就行了?这就是可持久化,详见代码。
代码
#include<bits/stdc++.h>
#define FOR(i,x,y) for(int i=(x),i##END=(y);i<=i##END;++i)
#define DOR(i,x,y) for(int i=(x),i##END=(y);i>=i##END;--i)
typedef long long LL;
using namespace std;
const int N=3e5+5;
const int NN=1e7+2e6+5;
struct ChairmanTree
{
int lson[NN],rson[NN];LL sum[NN];
int rt[N],tot;
int &operator [](const int x){return rt[x];}
void build()
{
memset(rt,0,sizeof(rt));
sum[tot=0]=lson[0]=rson[0]=0;
}
void create(int &k){sum[++tot]=sum[k],lson[tot]=lson[k],rson[tot]=rson[k],k=tot;}
void update(int &k,int x,int val,int l,int r)
{
create(k);
if(l==r)
{
sum[k]+=val;
return;
}
int mid=(l+r)>>1;
if(x<=mid)update(lson[k],x,val,l,mid);
else update(rson[k],x,val,mid+1,r);
sum[k]=sum[lson[k]]+sum[rson[k]];
}
LL query(int k,int L,int R,int l,int r)
{
if(!k)return 0;
if(L<=l&&r<=R)return sum[k];
int mid=(l+r)>>1;
if(R<=mid)return query(lson[k],L,R,l,mid);
else if(L>mid)return query(rson[k],L,R,mid+1,r);
else return query(lson[k],L,R,l,mid)+query(rson[k],L,R,mid+1,r);
}
}CT;
map<int,int>mp;
int main()
{
int T,n,m;
scanf("%d",&T);
while(T--)
{
mp.clear();
scanf("%d",&n);
CT.build();
FOR(i,1,n)
{
int x;
scanf("%d",&x);
CT[i]=CT[i-1];
if(mp[x])CT.update(CT[i],mp[x],-x,1,n);
CT.update(CT[i],i,x,1,n);
mp[x]=i;
}
scanf("%d",&m);
while(m--)
{
int x,y;
scanf("%d%d",&x,&y);
printf("%lld\n",CT.query(CT[y],x,y,1,n));
}
}
return 0;
}