题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3333
这个题目很好!!!真的很好!!!思维能力的锻炼!!!
题目意思:给你个数组,然后多次查询,每次查询一个区间,叫你求这个区间内所有不重复的数字之后;数据量很大,想用暴力是不可能的;
思路:线段树+离线处理;也就是说我先把你每次要查询的区间保存下来,并且将他们按右区间由小到大排好序,同时我们也保存题目提供的数组,
而且不暂时不更新到线段树里面,这里我们就得换个角度去想了,就这么简单的更新到树里面,那算得结果肯定是错误的啊,因为前面加过了;
我们形象的来说一下,我每次更新一个数字,我判断前面有没有出现过,如果有出现过,我就将前面的那个数字清零,并且将这个数字存在最后一次
出现的位置;但是这样有的人就会说,这样岂不是会改变后面的结果吗?这里就有一个技巧了,那就是我代码里面提到的那样,把区间先排序,这样我每次
更新到区间最靠左的那个区间,就直接先把结果算出来,后面在更新,然后再清零,这样对我数据就没有影响;
呼呼,说的有些啰嗦,好像还不是很清楚;可以结合我的代码加注释,理解一下;也可以结合一下这个链接里说的思路,还清晰;
链接:http://www.cnblogs.com/deadblue/archive/2012/09/13/2683199.html
求助一下大家,就是我那个为什么用lower_bound()函数会WA,而用我代码里的二分查找就可以AC,结果是一样的啊,希望可以不吝赐教;感谢。。。ORZ...
#include<iostream> #include<string> #include<cstdio> #include<cstring> #include<queue> #include<map> #include<set> #include<vector> #include<algorithm> #define LL long long using namespace std; const int N=30015; const int M=100015; struct node { int l,r; LL v; }node[N<<2]; struct NODE // 存储查询的区间,及下标; { int l,r,id; }temp[M]; void PushUp(int numb) // 向上更新父节点数据; { node[numb].v=node[numb<<1].v+node[numb<<1|1].v; } void build(int l,int r,int numb) // 建立线段数 { node[numb].l=l; node[numb].r=r; node[numb].v=0; if(l==r) return; int mid=(l+r)>>1; build(l,mid,numb<<1); build(mid+1,r,numb<<1|1); } void Insert(int numb,int t,int v) // 数据的更新,插入; { int l=node[numb].l,r=node[numb].r; if(l==r){ node[numb].v+=v; return; } int mid=(l+r)>>1; if(t>mid) Insert(numb<<1|1,t,v); else if(t<=mid) Insert(numb<<1,t,v); PushUp(numb); } LL query(int l,int r,int numb) // 区间查询; { if(l==node[numb].l&&r==node[numb].r) return node[numb].v; int mid=(node[numb].l+node[numb].r)>>1; if(l>mid) return query(l,r,numb<<1|1); else if(r<=mid) return query(l,r,numb<<1); else return query(l,mid,numb<<1)+query(mid+1,r,numb<<1|1); } bool cmp(NODE x,NODE y) // 自定义结构体排序; { return x.r<y.r; } int a[N],b[N],bb[N],visit[N]; LL ans[M]; int Is(int v,int k){ // 二分查找,其中v是要找的数,k表示数组的个数; int mid,l=1,r=k; while(l<=r){ mid=(l+r)/2; if(bb[mid]>v) r=mid-1; else if(bb[mid]<v) l=mid+1; else return mid; } return 0; } int main() { int t,m,n; scanf("%d",&t); while(t--){ scanf("%d",&n); build(1,30015,1); // 建树; for(int i=1;i<=n;i++){ scanf("%d",&a[i]); // 先将数据用数组保存; b[i]=a[i]; // 为了不破坏数据的顺序,再用一个数组, } sort(b,b+n+1); bb[1]=b[1]; int k=1; for(int i=2;i<=n;i++){ // 去重; if(b[i]!=b[i-1]){ bb[++k]=b[i]; } } memset(ans,0,sizeof(ans)); // 初始化; memset(visit,0,sizeof(visit)); scanf("%d",&m); for(int i=1;i<=m;i++){ // 先将要查询的数据用结构体保存; scanf("%d%d",&temp[i].l,&temp[i].r); temp[i].id=i; // 保存下标,这样排序后仍能够和结果相对应保存; } sort(temp,temp+m+1,cmp); // 按右区间由小到大排序; int j=1; for(int i=1;i<=n;i++){ //int id=lower_bound(bb,bb+n+1,a[i])-bb; // 第一个大于等于值a[i]的位置,用这个函数提交就WA,想了一下午也没有想明白为什么, int id=Is(a[i],k); // 返回该元素下标; 用这个写的二分查找就可以AC,ORZ...如果谁懂希望可以教我。。。跪求 if(visit[id]){ // 如果之前出现过,那么就将前面的清零;因为我们是计算区间不重复数字的和,所以对结果没有影响; Insert(1,visit[id],-a[i]); } Insert(1,i,a[i]); visit[id]=i; // 标记出现过,并且记录出现的位置; for(;j<=m;j++){ if(i==temp[j].r){ // 这就是我们为什么要讲区间排序的原因,因为只有这样我们才可以保证在清零之后不影响结果;所以将 LL sum=query(temp[j].l,temp[j].r,1); // 这个查询放在插入里面,只要插入的点有我要查找的右区间, ans[temp[j].id]+=sum; // 我就立马算出结果,避免后面出现的数字把之前的数据清零; }else break; } } for(int i=1;i<=m;i++) printf("%lld\n",ans[i]); } return 0; }