扫描线 亚特兰蒂斯

题目链接:247. 亚特兰蒂斯

算法分析

只说细节。

这道题目不是特别严格的线段树,因为线段树维护的信息具有区间可合并性,这道题目想了很久想不出可以区间直接合并的信息。我们在这里维护的是离散化后表示小段的数组,设为 a [ ] a[] a[]吧,对于一个边界的两个纵坐标,设为 y 1 y1 y1 y 2 y2 y2,离散化后的结果为 [ a l , a r ] [al,ar] [al,ar],这是点,如果要对应到小段,那么则是 [ a l , a r − 1 ] [al,ar-1] [al,ar1]

在每个区间结点上,设cnt表示该区间被完整扫过的次数,这里强调下,是完整扫过,那些子区间被扫过但本身没被完整扫过的不能累加。因此,满足不了区间可合并的特性。但是因为在树上,我们在更新线段树的时候,可以将结果从下往上累加,在每个结点上用len表示该区间被扫描线覆盖的长度。下面是重点:

当某个区间结点的cnt>0,则 t r [ p ] . l e n = v a l [ a r + 1 ] − v a l [ a l ] tr[p].len=val[ar+1] - val[al] tr[p].len=val[ar+1]val[al],因为线段树中维护的是小段,要计算的话,得转移到点上直接相减,所以右端点是 a r + 1 ar+1 ar+1

当某个区间结点的cnt=0,则它的len值是其左右儿子的len值之和。如果该点是叶结点,则len值重置为0。

if (tr[p].cnt)
		tr[p].len = val[ar+1] - val[al];
	else if (al != ar)
		tr[p].len = tr[2*p].len + tr[2*p+1].len;
	else tr[p].len = 0; 

该边界被扫描线覆盖的长度就是 t r [ 1 ] . l e n tr[1].len tr[1].len。不需要查询。事实上,这种做法下,查询了反而是错的。如果查询 [ a l , a r ] [al,ar] [al,ar],结果代表扫描线在区间 [ a l , a r + 1 ] [al,ar+1] [al,ar+1]被覆盖的长度,和要求不符,如果查询 [ 1 , c n t − 1 ] [1, cnt-1] [1,cnt1],有可能该结点的cnt为0,造成漏查。

根据以上分析,就无需下传标记了。

离散化的时候,除了要记录离散化后的整数对应原来的实数,还要记录原来的实数对应的整数。后者用map实现。也可以在lsh[]中用 l o w e r _ b o u n d lower\_bound lower_bound重新查询下位次。

map<double, int> lshval;
sort(lsh + 1, lsh + t + 1);
int cnt = unique(lsh + 1, lsh + t + 1) - lsh - 1;
for (int i = 1; i <= t; ++i)
{
	double tem = aa[i];
	a[i] = lower_bound(lsh + 1, lsh + cnt + 1, aa[i]) - lsh;
	val[a[i]] = tem;
	lshval[tem] = a[i];
}	

注意:离散化之前要对lsh数组排序。

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
#include <map>
#include <vector>
using namespace std;
#define ll long long 
const int N = 1e4 + 10;
double aa[2*N], val[2*N], lsh[2*N];
int a[2*N];
int n;
map<double, int> lshval;
struct dot
{
	double x, y1, y2;
	int id;
}d[2*N];
struct SegmentTree
{
	double len;
	int cnt;
}tr[8*N];
double relf()
{
	double x = 0, y = 0.1, f = 1; char c = getchar();
	while (c < '0' || c > '9') {if (c == '-') f = -1; c = getchar();}
	while (c >= '0' && c <= '9'){x = x * 10 + c - '0'; c = getchar();}
	if (c == '.')
	{
		c = getchar();
		while (c >= '0' && c <= '9'){x += y * (c - '0'); y /= 10; c = getchar();} 
	}
	return x * f; 
}
bool cmp(dot a, dot b)
{
	return a.x < b.x;
}
void szbuild(int p, int l, int r)
{
	tr[p].len = tr[p].cnt = 0;
	if (l == r) return;
	int mid = (l + r) >> 1;
	szbuild(2 * p, l, mid);
	szbuild(2 * p + 1, mid + 1, r);	
}
void szchange(int p, int al, int ar, int ql, int qr, int num)
{
	if (ql > ar || qr < al) return;  // 完全没交集,直接返回 
	if (ql <= al && ar <= qr)
	{
		tr[p].cnt += num;
		if (tr[p].cnt)
			tr[p].len = val[ar+1] - val[al];
		else if (al != ar)
			tr[p].len = tr[2*p].len + tr[2*p+1].len;
		else tr[p].len = 0;  // 没有覆盖了,叶节点置为0 ,这点很重要  
		return;
	} 
	int mid = (al + ar) >> 1;
	szchange(2 * p, al, mid, ql, qr, num);
	szchange(2 * p + 1, mid + 1, ar, ql, qr, num);
	if (tr[p].cnt)
		tr[p].len = val[ar+1] - val[al];
	else if (al != ar)
		tr[p].len = tr[2*p].len + tr[2*p+1].len;
	else tr[p].len = 0;  // 
}
int main()  // 标记不下传  
{
	int T = 0;
	while (1)
	{
		++T;
		scanf("%d", &n);
		if (!n) break;
		int t = 0;
		double ax, ay, bx, by;
		for (int i = 1; i <= 2 * n; i += 2)
		{
			ax = relf(); ay = relf(); bx = relf(); by = relf();
			d[i].x   = ax; d[i].y1   = ay;  d[i].y2   = by; d[i].id = 1;
			d[i+1].x = bx; d[i+1].y1 = ay;  d[i+1].y2 = by; d[i+1].id = -1;
			aa[++t] = ay; aa[++t] = by;
		}
		// 离散化  	
		memcpy(lsh, aa, sizeof(aa));
		sort(lsh + 1, lsh + t + 1);
		int cnt = unique(lsh + 1, lsh + t + 1) - lsh - 1;
		for (int i = 1; i <= t; ++i)
		{
			double tem = aa[i];
			a[i] = lower_bound(lsh + 1, lsh + cnt + 1, aa[i]) - lsh;
			val[a[i]] = tem;
			lshval[tem] = a[i];
		}	
		// 建线段树维护小段,第i小段代表段val[i+1] - val[i]
		// 总共有cnt-1段  
		szbuild(1, 1, cnt - 1);
		// 
		double ans = 0; 
		sort(d + 1, d + 2 * n + 1, cmp);  // 按照x坐标排序  
		for (int i = 1; i <= 2 * n - 1; ++i)
		{
			// [ql, qr]区间是要更新的区间  
			int ql = lshval[d[i].y1], qr = lshval[d[i].y2] - 1;	
			szchange(1, 1, cnt - 1, ql, qr, d[i].id);
			ans += (d[i+1].x - d[i].x) * tr[1].len;
		}
		printf("Test case #%d\n", T);
		printf("Total explored area: %.2lf\n\n", ans);
	}
	return 0;
}

反思与总结

  1. 码代码的时候要集中精力,第一遍的错误,很难发现纠正。尽量一遍过。

  2. 注意快读正负实型的写法。

### 下载 Popper.min.js 文件的方法 对于希望获取 `popper.min.js` 的开发者来说,可以通过多种方式来实现这一目标。通常情况下,推荐通过官方渠道或可靠的分发网络 (CDN) 来获得最新的稳定版文件。 #### 使用 CDN 获取 Popper.min.js 最简单的方式之一是从流行的 CDN 中加载所需的 JavaScript 库。这不仅简化了集成过程,还可能提高性能,因为许多用户已经缓存了来自这些服务提供商的内容。例如: ```html <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2/dist/umd/popper.min.js"></script> ``` 这种方式不需要手动下载文件到本地服务器;只需将上述 `<script>` 标签添加至 HTML 文档中的适当位置即可立即使用 Popper 功能[^1]。 #### 从 npm 或 yarn 安装 如果项目采用模块化构建工具链,则可以直接利用包管理器如 npm 或 Yarn 进行安装。命令如下所示: ```bash npm install @popperjs/core # 或者 yarn add @popperjs/core ``` 之后可以根据具体需求引入特定功能模块,而不是整个库,从而减少打包后的体积并优化加载速度[^2]。 #### 访问 GitHub 发布页面下载压缩包 另一种方法是访问 Popper.js 的 [GitHub Releases](https://github.com/popperjs/popper-core/releases) 页面,在这里可以选择不同版本的 tarball 或 zip 归档进行下载解压操作。这种方法适合那些偏好离线工作环境或是想要定制编译选项的人群[^3]。 #### 手动克隆仓库 最后一种较为少见但也可行的办法便是直接克隆完整的 Git 存储库副本。这样可以获得开发分支以及历史记录等更多信息,适用于贡献代码或者深入学习内部机制的情况。 ```bash git clone https://github.com/popperjs/popper-core.git cd popper-core ``` 完成以上任一途径后便能成功取得所需版本的 Popper.min.js 文件,并将其应用于个人项目之中[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值