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;
}