这题没注意看数据上来就用莫队t了一发。然后仔细一看发现不太好做,想了很久。
题目链接:点击打开链接
题意:t组样例,t<10,n个数,q个询问,每次给一个区间,问这个区间不重复的数字的和。n<=30000,q<=100000。
那么,如果我们事先直接把[1,n]这个区间的线段树建立起来,其实把很多重复的数字都放了进去。考虑一下,先不把线段树建好。
只要我们能保证之前处理的所有区间的右边界都在现在区间的右边界之前,那么我们就只需要让那些重复数字尽量往后靠,就不会对我现在的区间产生影响。这道题就可以迎刃而解,这道题目很有想法。
然后将q个询问按照右边界从小到大 再按照左边界从小到大排序,我们就只需要遍历一遍n数组,就能在(n+q)logn的复杂度内处理完所有询问。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
map<ll,int> mp;
struct Node{
int id,l,r;
}node[100000+10];
ll ans[100000+10];
ll num[30000+10];
bool cmp (Node a,Node b)
{
if(a.r!=b.r){
return a.r<b.r;
}
return a.l<b.l;
}
ll tree[30005*4];
void update(int rt,int L,int R,int x,ll v)
{
if(L==R&&L==x){
tree[rt]=v;
}
else{
int mid=(L+R)>>1;
if(x>mid) update((rt<<1)+1,mid+1,R,x,v);
else update((rt<<1),L,mid,x,v);
tree[rt]=tree[rt<<1]+tree[(rt<<1)+1];
}
}
ll query(int rt,int L,int R,int l,int r)
{
if(L>r||R<l) return 0;
if(L>=l&&R<=r) return tree[rt];
int mid=(L+R)>>1;
return query(rt<<1,L,mid,l,r)+query((rt<<1)+1,mid+1,R,l,r);
}
int main(void)
{
//freopen("D:\\GYM\\ACM\\duipai\\data.txt","r",stdin);
//freopen("D:\\GYM\\ACM\\duipai\\out2.txt","w",stdout);
int t;
scanf("%d",&t);
while(t--){
memset(tree,0,sizeof(tree));
mp.clear();
int n,q;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&num[i]);
}
scanf("%d",&q);
for(int i=0;i<q;i++){
node[i].id=i;
scanf("%d%d",&node[i].l,&node[i].r);
}
sort(node,node+q,cmp);
int pos=0;
for(int i=0;i<q;i++){
while(pos<node[i].r){
pos++;
int pre=mp[num[pos]];
if(pre!=0){
update(1,1,n,pre,0);
}
update(1,1,n,pos,num[pos]);
mp[num[pos]]=pos;
}
ans[node[i].id]=query(1,1,n,node[i].l,node[i].r);
}
for(int i=0;i<q;i++){
printf("%lld\n",ans[i]);
}
}
return 0;
}