http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=193991
//去重离散化+离线处理思想 //题意是 【取1,N】数组中a为起点的后缀数组,映射为字典序最小的序列,求该序列第b个元素的映射 //Gym 100496D 其实就是求位置[a,b]中,b位置对应的元素在区间最靠左的位置 前面有多少种不同的元素,有点绕口。 //对于【前面有多少种不同的元素】我们可以用前缀和数组记录处理一下即可得到,但是要注意这个前缀和的起点必须是a,如果在a以前出现过1种元素,那么他会被a以后的点累计进去,但是这个元素如果只在a前出 现,在[a,b]是不存在的,那么这个前缀和就错了! 又发现题目是可以用离线做法的,先把全部区间排序 从左端点最靠右的区间开始处理,求该区间种类前缀和,计算答案,储存,然后再处理左端点第二靠右的区间 ,这样得到的前缀和就是正确的了,, 因为左端点靠左的区间先处理了,会影响左端点靠右的区间的答案,反之不会(在图上画画比较清楚)。。然后问题就是...虽然要前缀和,但是每次求一边前缀和当然也是超 时啦,显然是要用树状数组。 开一个vis数组标记该元素是否出现过 (因为数组是10^9,所以必须离散化) 区间长度为[N],对区间【a,b】,从b-for 到a 遇到已经出现过的元素,就把该元素的【最靠左位置】更新为当前位置;如果遇到未出现过 的,就标为出现了,同时更新【最靠左位置】 for完后,区间[a,b]中【b位置对应的元素在区间最靠左的位置 】的b位置的元素对应的【最靠左位置】(已记录),它【前面有多少种不同的元素】 就是get_sum(【b位置对应的元素在区间最靠左的位置 】) #include <cstdio> #include <cmath> #include <cstring> #include <string> #include <algorithm> #include <iostream> #include <queue> #include <map> #include <set> #include <vector> using namespace std; #define inf 0x7f7f7f7f; const int maxn=100000*2+5; int n,m; struct node { int x,num; //把原始数据 的值 与原始序号储存 } A[maxn]; struct edge { int x,y,num; } tm[maxn]; int cmp(node a,node b) { return a.x<b.x; //把原始数据按值排序, } int posi[maxn]; int vis[maxn]; int max(int a,int b ) { if (a<b)return b;return a; } int tree[maxn]; inline int lowbit(int x) { return x&-x; } void add(int x,int value) { for (int i=x;i<=n;i=i+lowbit(i)) { tree[i]+=value; } } int get(int x) { int sum=0; for (int i=x;i;i-=lowbit(i)) { sum+=tree[i]; } return sum; } int cmp2(edge a,edge b) { if (a.x!=b.x) return a.x>b.x; else return a.y<b.y; } int ans[maxn]; int main() { freopen( "data.in","r",stdin ); //scanf 从1.txt输入 freopen( "data.out","w",stdout ); //printf输出到1.tx int i,a,b,j; cin>>n; for (i=1;i<=n;i++) { scanf("%d",&A[i].x); A[i].num=i; } sort(A+1,A+1+n,cmp); //对原始数据按值排升序 int ok=0; for (i=1;i<=n;i++) //对于排序好的数据,离散化 { ok++; if (A[i].x==A[i-1].x) //如果值相同,则离散化后的新坐标相同 { ok--; posi[A[i].num]=ok; } else posi[A[i].num]=ok; } cin>>m; int maxx=0; for (i=1;i<=m;i++) { scanf("%d%d",&tm[i].x,&tm[i].y); tm[i].y+=tm[i].x-1; //直接把【a为起点的后缀数组中第b个元素】转化为【整个数组中的第a+b-1个位置】 tm[i].num=i; //记录边的序号 maxx=max(maxx,tm[i].y); //取得最大右端点 } sort(tm+1,tm+1+m,cmp2); //此处是关键,尽量先选左端点靠右的区间 因为【1,3】 【2,4】 先计算【2,4】,对待会计算【1,3】结果不影响,反之会影响; int mark_l=maxx; vis[ posi[mark_l] ]=mark_l; //先把最右端点处理了。 add(mark_l,1); for (i=1;i<=m;i++) { a=tm[i].x; b=tm[i].y; if (a<mark_l) //如果a>=mark_l 表示当前区间已经在之前就被处理过了,直接得出答案即可。 { for (j=mark_l-1;j>=a;j--) //处理【上一次的mark_l到a】 { if (vis[ posi[j] ]==0) //该点未用过 { vis[ posi[j] ]=j; add(j,1); } else { int t=vis[ posi[j] ]; add(t,-1); vis[ posi[j] ]=j; add(j,1); } } } // cout<<get(vis[posi[b]])<<endl; ans[tm[i].num]=get( vis[posi[b]]); mark_l=a; } for (i=1;i<=m;i++) { printf("%d\n",ans[i]); } return 0; }