D - Data Mining-Gym 100496D-离线处理+树状数组+离散化

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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值