题目链接:https://cn.vjudge.net/problem/HDU-3333
题意:区间查询i-j内不同数之和
思路:查找不同数不可能每次查询到叶子节点,必然会t,那么就离线处理:
1.将n个数的值及其下标保存,按下标排序。
2.将q个查询及其下标保存,按查询的右端点排序。
3.从前往后对于每个查询,插入下标<=它的元素,每次插入前判断一下,这个元素的值是否已经存在于线段树中(利用map判断,map中存储每个值对应元素的位置),若它已经存在,将原来的更新为0,保留现在的。(贪心思想,每个区间查询,两个值相同的元素只会被算一次,离r越近的元素越有可能被算到)。然后进行区间查询,按查询的下标保存结果。
4.按查询下标从小到大输出结果。
代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<map>
using namespace std;
typedef pair<int,int> pa;
typedef long long ll; //注意所有元素加起来可能会爆int,要用long long存
const int N =3e4+10;
ll sum[N<<2],ans[100005]; //注意查询的个数比N要大,第一次这个ans数组开小了,还wa了一发...
pa a[N];
struct node
{
int l,r,pos;
}q[100005];
void pushup(int x)
{
sum[x]=sum[x<<1]+sum[x<<1|1];
}
void build(int x,int l,int r)
{
sum[x]=0;
if(l==r)
return;
int mid=l+r>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
void update(int x,int l,int r,int tem,int val)
{
if(l==r)
{
sum[x]=val;return;
}
int mid=l+r>>1;
if(tem<=mid)
update(x<<1,l,mid,tem,val);
else
update(x<<1|1,mid+1,r,tem,val);
pushup(x);
}
ll query(int x,int L ,int R,int l,int r)
{
if(L<=l && r<=R)
return sum[x];
int mid=l+r>>1;
ll ans=0;
if(L<=mid)
ans+=query(x<<1,L,R,l,mid);
if(R>mid)
ans+=query(x<<1|1,L,R,mid+1,r);
return ans;
}
bool cmp(node x,node y)
{
return x.r<y.r;
}
int main()
{
int t,n,m;
cin>>t;
while(t--)
{
cin>>n;
build(1,1,n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i].second);
a[i].first=i;
}
sort(a+1,a+n+1);
cin>>m;
for(int i=0;i<m;i++)
{
int l,r;
scanf("%d%d",&l,&r);
q[i]={l,r,i+1};
}
sort(q,q+m,cmp);
map<int,int> mp;
for(int i=0,j=1;i<m;i++)
{
int l=q[i].l,r=q[i].r;
while(a[j].first<=q[i].r && j<=n)
{
int x=a[j].first,val=a[j].second;
if(mp[val])
{
update(1,1,n,mp[val],0); //将重复元素下标较小的清空
mp[val]=x;
update(1,1,n,x,val);
}
else
{
mp[val]=x;
update(1,1,n,x,val);
}
j++;
}
ans[q[i].pos]=query(1,l,r,1,n);
}
for(int i=1;i<=m;i++)
printf("%lld\n",ans[i]);
}
return 0;
}