题目大意:先给出n个数字。面对q个询问区间,输出这个区间不同数的和。
题解:这道题有几种解法。这里讲一下用分块解决的方法。( 离线树状数组解法看这里 Hdu-3333 Turning Tree (离线树状数组/线段树)~~~)
以(a[i]上一次出现的位置last, a[i])数字对作为基本元素做分块。那么容易想到对于每一个询问(l,r),如果该元素的last<l那么这个元素就应该加入到ans中。 那么我们对于每一个分块,以last作为关键字排序。对于每一次询问。左右两端的分块就枚举last<l的a[i]加入到ans中,中间的分块因为排序的原因,last是有序的。那么中间的分块直接二分询问的左端点l,该分块左端点到二分得到的点都是符合条件的。
AC代码如下,因为用结构体做分块的原因。代码写得稀烂~~~。
#include<iostream> #include<cstdio> #include<cmath> #include<map> #include<algorithm> using namespace std; const int N=30000+10; const int SqrtN=200; struct data{ int last,v; }a[N],b[N]; long long sum[N]; int L[SqrtN],R[SqrtN]; int pos[N]; int n,m,t; bool cmp1(data x1,data x2) { return x1.last<x2.last; } map<int,int> lst; void init() { lst.clear(); for (int i=1;i<=n;i++) { a[i].last=lst[a[i].v]; lst[a[i].v]=i; } for (int i=1;i<=n;i++) b[i]=a[i]; t=sqrt(n); for (int i=1;i<=t;i++) { L[i]=(i-1)*t+1; R[i]=i*t; } if (R[t]<n) t++,L[t]=R[t-1]+1,R[t]=n; for (int i=1;i<=t;i++) sort(a+L[i],a+R[i]+1,cmp1); for (int i=1;i<=t;i++) { for (int j=L[i];j<=R[i];j++) { pos[j]=i; if (j==L[i]) sum[j]=a[j].v; else sum[j]=sum[j-1]+a[j].v; } } } struct cmp2 { bool operator () (const data &a,const data &b) const { return a.last < b.last; } }; long long query(int l,int r) { int p=pos[l],q=pos[r]; long long ans=0; if (p==q) { for (int i=l;i<=r;i++) if (b[i].last<l) ans+=b[i].v; } else { for (int i=l;i<=R[p];i++) if (b[i].last<l) ans+=b[i].v; for (int i=L[q];i<=r;i++) if (b[i].last<l) ans+=b[i].v; for (int i=p+1;i<=q-1;i++) { data temp; temp.last=l; temp.v=-1; int x=lower_bound(a+L[i],a+R[i]+1,temp,cmp2())-a; if (x!=L[i]) ans+=sum[x-1]; } } return ans; } int main() { int T; scanf("%d",&T); while (T--) { scanf("%d",&n); for (int i=1;i<=n;i++) scanf("%d",&a[i].v); init(); scanf("%d",&m); for (int i=1;i<=m;i++) { int l,r; scanf("%d%d",&l,&r); printf("%lld ",query(l,r)); } } return 0; }