hdu 4417 Super Mario(哈希表+树状树组)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4417


题意:给出一个长度为n的数列,询问m次,L到R之间不大于H的数的个数。

解析:暴力是没戏,考虑的时候我们希望得到这样的数据,array[i]表示i个元素之前小于H的元素个数(对任意H),这样每次询问结果就是array[R]-array[L],用求逆序数的思想,利用树状数组可以求出在第i个元素之前有多少个小于H的,这样我们可以从小到大枚举每一个i,要求的是,对于区间左端点等于i的询问,求出i之前有多少个小于对应区间的H,对于区间右端点等于i的,求出i之前有多少个小于H的,更新完之后,每个区间的左右端点对应的值肯定都求出来了,右端点减去左端点的值即为结果。在找哪些区间端点等于i的时候最好用vector存一下,具体代码中再解释吧,这题不太好说,另外数据比较大,需要对H的值哈希一下。

自己再多想想啊,这题目不太好解释。

参考代码:


#include<iostream>
#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;

#define clr(arr,v) memset(arr,v,sizeof(arr))
const int M = 100500;

template<int size,int Max>
class Hash{
public:
	Hash(){}
	void clear(){
		clr(h,-1);
		pos = 0;
	}
	int& operator[](int x){
		return val[ add(x) ];
	}
	int add(int x){
		int cur = x % Max,value = x / Max;
		for(int i = h[cur]; i != -1 ;i = next[i])
		{
			if(num[i] == value) return i;
		}
		num[++pos] = value;
		next[pos] = h[cur];
		h[cur] = pos;
		return pos;
	}
private:
	int h[Max],num[size],next[size],val[size],pos;
};
Hash<M,1000007> h;

struct segment{
	int left,right,H,index;
	int value_l,value_r;
}seg[M];

struct point{
	point(){}
	point(int _index,int _H,int _or):index(_index),H(_H),or(_or){}
	int index,H,or;
};
vector<point> vec[M];

int tree[M],num[M],data[M],pos;

void read(int n,int m)
{
	clr(seg,0);
	for(int i = 0;i <= n;++i)
		vec[i].clear();
	pos = 0;
	for(int i = 1;i <= n;++i)
	{
		scanf("%d",&data[i]);
		num[pos++] = data[i];
	}
	for(int i = 0;i < m;++i)  
	{
		scanf("%d%d%d",&seg[i].left,&seg[i].right,&seg[i].H);
		seg[i].left++;
		seg[i].right++;
		seg[i].index = i;
		vec[ seg[i].left ].push_back(point(seg[i].index,seg[i].H,0));
		vec[ seg[i].right ].push_back(point(seg[i].index,seg[i].H,1));
		num[pos++] = seg[i].H;
	}
}

int lowbit(int x) { return x&-x; }
int get_num(int x,int n)
{
	if(x >= n) return tree[n];
	return tree[x] + get_num(x+lowbit(x),n);
}
void update(int x)
{
	if(x == 0) return ;
	tree[x]++;
	update(x-lowbit(x));
}

void solve(int n,int m)
{
	clr(tree,0);
	int index = 0,res;
	sort(num,num+pos);
	for(int i = 0;i < pos;++i)  //哈希
	{
		if(i == 0 || num[i] != num[i-1])
			h[ num[i] ] = ++index;
		else
			h[ num[i] ] = index;
	}
	for(int i = 1;i <= n;++i)
	{
		for(int j = 0;j < vec[i].size();++j)
		{
			if(vec[i][j].or == 0)
			{
				res = i - 1 - get_num(h[ vec[i][j].H ],index); //计算的是array[R]-array[L-1]的值,所以是i-1
				seg[ vec[i][j].index ].value_l = res; //value_l表示L之前小于H的个数
			}
		}
		update(h[ data[i] ]-1); //要在计算完左端点后更新
		for(int j = 0;j < vec[i].size();++j)
		{
			if(vec[i][j].or)
			{
				res = i - get_num(h[ vec[i][j].H ],index);
				seg[ vec[i][j].index ].value_r = res;    // value_r表示R(包含)之前小于H的个数
			}
		}
	}
	for(int i = 0;i < m;++i)
		printf("%d\n",seg[i].value_r - seg[i].value_l);
}

int main()
{
	//freopen("1.txt","r",stdin);
	int T,n,m,ncase = 1;
	scanf("%d",&T);
	while(T--)
	{
		h.clear();
		scanf("%d%d",&n,&m);
		read(n,m);
		printf("Case %d:\n",ncase++);
		solve(n,m);
	}
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值