POJ 2528 - Mayor's Poster | 漂浮法 | 线段树

题目:传送门
题意:有N张海报,每张海报会占据[l, r]的宽度,后来者贴在前者上面。问N张贴完后,共有多少张可见(没被覆盖)。T组输入。

卡了三天终于想明白了ORZ

线段树

这是一个很经典的区间覆盖问题。想到用线段树并不困难,但是这里会出现两个问题。
第一是节点参数的设置。最开始我的想法是,设置代表先后顺序的“时间高度”maxheight, rightheight, leftheight。其实只要是后来的海报,一定会比该节点的maxheight大,覆盖。主要问题是在合并的时候,我只注意到左右子节点合并时边界的问题。如果lson的右端点和rson的左端点是同一张海报,那么他们就加和-1(通过对rightheight, leftheight的判断);反之,不同海报,直接加和。但是左右节点相同的海报不一定只有节点处的一张。如果有一个很早就贴上的跨越整个父节点区间的海报,又有一张后来的跨越左右节点边界的海报,其实父节点处的sum值应该是2,但是这样计算结果是3。因为是自己在写题,一旦最开始思维固定了,就很难发现自己漏掉了情况,所以这个Bug从周六一直到今天,我单步执行一点点抠才发现为什么我的sum这么诡异……(最开始没有设置push_down,是测试(1,10)(1,4)(6,10)的时候意识到push_down不可以省略)。
其实这道题还有一点我自己理解起来很别扭的地方,那就是这里的海报,其实相当于“染色”。线段树管理的其实不是连续的区间,而是连续的点。举例来说,(1,9)(1,4)(5,9)。如果是从线段的角度看待,那其实4-5中间除端点的部分会有一截露出第一种颜色。但是!我们的线段树节点管理的是。应当想成,有9个点,对1号到9号染色成第一种颜色。之后对1号到4号染色成第二种颜色。对5号到9号染色成第三种。这样就不难理解为何最后结果是2种颜色。

第二点,这里的点的范围给的是1e7,但是空间限制是65536K,如果直接写线段树。1e7*4,是会MLE的。这里就需要离散化。我们观察数据范围和数据组数,如果范围>>组数,那么一般就需要离散化。离散化:针对点的个数很少,但数据范围很大的情况。具体见这里:传送门

漂浮法

在线段树把我RE爆之后,发现了这个神奇的新方法(菜鸡第一次听说)。漂浮法主要针对数据比较弱的区间覆盖问题,可以帮助我们避免线段树的码量(漂浮法代码简洁,占用空间小)。这道题就可以用漂浮法水过去。
漂浮法顾名思义,我们可以把区间覆盖看成水下的飞毯。后来的就漂在上面。我们怎么确定最后在水面上看能看到多少个飞毯呢?如果一个飞毯从他自己的位置向上漂浮,至少有一部分可以漂浮到水面(如果遇到部分被上面的重叠,那就把飞毯剪开,不重叠的继续往上漂),那这个飞毯一定是可见的。利用这种想法,我们只要从1到N扫一遍,就可以通过ans统计出可见飞毯的总数了!
并且因为漂浮法只是比较大小,所以不需要离散化

#define _CRT_SECURE_NO_WARNINGS
#include <cstdlib>
#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

#define MAX_N 100010 //区间数目
#define ll long long 

int T, N;
int lsh[MAX_N << 4];
ll ans;
struct qujian
{
	int l, r;
}in[MAX_N << 4];

bool float_fun(int left, int right, int i)
{
	while (i <= N && (left > in[i].r || right < in[i].l))
		i++;

	if (i > N)
		return true;
	if (in[i].l <= left && in[i].r >= right)
		return false;

	if (left < in[i].l)//左边有露出来还可以漂的部分
	{
		if (float_fun(left, in[i].l - 1, i + 1))
			return true;
	}

	if (right > in[i].r)//右边有露出来可以继续漂的部分
	{
		if (float_fun(in[i].r + 1, right, i + 1))
			return true;
	}

	return false;
}


int main()
{
	scanf("%d", &T);
	while (T--)
	{
		scanf("%d", &N);

		memset(lsh, 0, sizeof(lsh));
		ans = 0;

		int l, r, cnt = 0;
		for (int i = 1; i <= N; i++)
		{
			scanf("%d %d", &l, &r);
			in[i].l = l, in[i].r = r;
			lsh[++cnt] = l, lsh[++cnt] = r;
		}

		sort(lsh + 1, lsh + cnt + 1);

		cnt = unique(lsh + 1, lsh + cnt + 1) - lsh - 1;

		int m = cnt;
		for (int i = 1; i < m; i++)
		{
			if (lsh[i + 1] - lsh[i] > 1)
				lsh[++cnt] = lsh[i] + 1;
		}

		sort(lsh + 1, lsh + cnt + 1);

		/*printf("\n");
		for (int i = 1; i <= cnt; i++)
			printf("%d ", lsh[i]);
		printf("\n");*/

		for (int i = 1; i <= N; i++)
		{
			in[i].l = lower_bound(lsh + 1, lsh + cnt + 1, in[i].l) - lsh;
			in[i].r = lower_bound(lsh + 1, lsh + cnt + 1, in[i].r) - lsh;

			//printf("in[%d].l = %d  in[%d].r = %d\n", i, in[i].l, i, in[i].r);
		}

		for (int i = 1; i <= N; i++)
		{
			if (float_fun(in[i].l, in[i].r, i + 1))
				ans++;
		}

		printf("%lld\n", ans);
	}
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值