hdu 4417 离线 + 树状数组

这道题目可以用线段树也可以用树状数组,还可以用划分树(不会T T),前两者要离线完成,划分数不太清楚=。 =

先吐槽一下:由于树状数组今天才刚刚会没多久……于是悲剧的由于update的上限弄错而WA了一版…查错查了N久N久……

题解:

根据队里面讨论,离线也有两种做法。

先讲第一种做法:

先把所有数据都进来,之后把出现的障碍物的高度(包括n个障碍物以及m个查询的跳跃高度h)全部离散化,否则会因为H太大而无法用树状数组(or 线段树)

对于线段树来说,首先要清楚查询某个区间比H高度小的个数的操作。即Getsum(右端点) - Getsum(左端点-1)

如果直接对每个操作都直接重新读进相应区间的障碍物的高度的话则必然TLE。于是要排序

首先对操作的左端点排序,然后每当要统计一个Getsum(左端点-1)的时候,要先把左端点-1前面的障碍物全部读入。这样,遍历一次n就可以算出所有的Getsum(左端点-1)

右端点同上

最后用上面Getsum(右端点) - Getsum(左端点-1)这个公式即可求出答案。

第二种做法:

这种做法相对更简单一些,也貌似更巧妙一些(额,这次以线段树为例)

首先分别对H和障碍物的序列进行排序,之后每次检索比H小的 能跳过去的 障碍物的之前, 先把障碍物中比H小的先放进线段树(单点更新)(由于数组里面存在的数一定比检索的H值小,所以只要将障碍物所在位置+1,则之后检索的时候用线段树的区间查询(或者树状数组区间查询)即可。)之后用区间求和,得出的就是答案。

额……实现略。


照例温馨提示:除非实在理解不了我上面写得题解,否则不建议看下面代码

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <map>
#define LL long long
using namespace std;
const int Max = 1e5+100;
int n,m;
struct po
{
	int val;
	int id;
	bool operator < (const po &p) const
	{
		return val < p.val;
	}
}point[Max<<1];
int wall[Max];
int level[Max<<1];
int Max_n;
struct opera
{
	int a,b,h;
	int id;
}op[Max];
map<int , int> q;
bool cmp(const opera& p, const opera &q)
{
	return p.a < q.a;
}
bool cmq(const opera& p, const opera &q)
{
	return p.b < q.b;
}
inline int lowbit(int x)
{
	return x & (-x);
}

void update(int add, int x)
{
	while(x <= Max_n)
	{
		level[x] += add;
		x += lowbit(x);
	}
}

LL Getsum(int x)
{
	LL ans = 0;
	while(x > 0)
	{
		ans += level[x];
		x -= lowbit(x);
	}
	return ans;
}
LL Left[Max];
LL Right[Max];
int main()
{
	//freopen("1008.txt","r",stdin);
	int t,cases=0;
	scanf("%d",&t);
	while(t--)
	{
		q.clear();//初始化
		memset(wall, 0, sizeof(wall));
		memset(Left, 0, sizeof(Left));
		memset(Right, 0, sizeof(Right));
		Max_n = -1;

		printf("Case %d:\n",++cases);
		scanf("%d %d",&n,&m);
		for(int i=1; i<=n ;i++)
		{
			scanf("%d",&point[i].val);
			point[i].id = i;
		}
		for(int i=1; i<=m; i++)
		{
			scanf("%d %d %d",&op[i].a,&op[i].b,&op[i].h);
			op[i].a++;op[i].b++;
			op[i].id = i;
			point[n+i].val = op[i].h;
			point[n+i].id = n+i;
		}
		sort(point+1, point+1+n+m);
		int w = 0;
		for(int i=1; i<=m+n; i++)//离散化
			if(!q[point[i].val])
				q[point[i].val] = ++w;
		Max_n = w;
		for(int i=1; i<=m+n; i++)
			if(point[i].id <= n)
				wall[point[i].id] = q[point[i].val];
		for(int i=1; i<=m; i++)
			op[i].h = q[op[i].h];

		sort(op+1, op+1+m, cmp);//左端点排序
		memset(level, 0, sizeof(level));
		int tem_m = 1;
		for(int i=1; i<=n; i++)
		{
			if(tem_m > m)	break;
			update(1 , wall[i]);
			while(tem_m<=m && op[tem_m].a-1 <= i)
			{
				if(op[tem_m].a-1 != 0)
					Left[op[tem_m].id] = Getsum(op[tem_m].h);
				tem_m++;
			}
		}

		sort(op+1, op+1+m, cmq);//右端点排序
		memset(level, 0, sizeof(level));
		tem_m = 1;
		for(int i=1; i<=n; i++)
		{
			if(tem_m > m)	break;
			update(1 , wall[i]);
			while(tem_m<=m && op[tem_m].b <= i)
			{
				Right[op[tem_m].id] = Getsum(op[tem_m].h);
				tem_m++;
			}
		}

		for(int i=1; i<=m; i++)
		{
			printf("%I64d\n",Right[i] - Left[i]);
		}
	}
	return 0;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值