POJ3293 Rectilinear polygon - 平面扫描 - 坐标离散化 - 树状数组维护扫描线

Rectilinear polygon

Time Limit: 1000MS   Memory Limit: 65536K
Total Submissions: 2237   Accepted: 283

Description

Given is n points with integer coordinates in the plane. Is it is possible to construct a simple, that is non-intersecting, rectilinear polygon with the given points as vertices? In a rectilinear polygon there are at least 4 vertices and every edge is either horizontal or vertical; each vertex is an endpoint of exactly one horizontal edge and one vertical edge. There are no holes in a polygon.

样例中对应的图形

Input

The first line of input is an integer giving the number of cases that follow. The input of each case starts with an integer 4 ≤ n ≤ 100000 giving the number of points for this test case. It is followed by n pairs of integers specifying the x and y coordinates of the points for this case.

Output

The output should contain one line for each case on input. Each line should contain one integer number giving the length of the rectilinear polygon passing throught the given points when it exists; otherwise, it should contain -1.

Sample Input

1
8
1 2
1 0
2 1
2 2
3 2
3 1
4 0
4 2

Sample Output

12

 
 

题目大概意思:

给出 n ( 4 ≤ n ≤ 1 0 5 ) n(4≤n≤10^5) n(4n105) 个点的坐标,坐标均可表示为 i n t int int 型。要求以这些点为端点构造多边形,每一个点连接一条水平的边与一条竖直的边,边之间不能相交。问构造出的多边形的周长,若不存在则输出 − 1 -1 1 .

 
 

分析:

本题使用坐标离散化+平面扫描算法解决。

首先将所有点的坐标离散化处理,并将所有点按照 x x x 为第一关键字, y y y 为第二关键字从小到大排序,并用垂直于 x x x 轴的扫描线以 x x x 坐标从小到大扫描。

s u m ( y ) sum(y) sum(y) 为当前扫描线上,纵坐标小于等于 y y y 的半平面上与 y y y 轴垂直的边的个数,则有偶数条边(包括 0 0 0 条边)则表明扫描线上该点位于多边形外部,奇数条则位于内部,由于某一位置 y ′ y' y 添加一条与 y y y 轴垂直的边后,所有 y ≥ y ′ y≥y' yy s u m ( y ) sum(y) sum(y) 的值都会增加 1 1 1 ,因此可以采用树状数组的区间修改单点查询的方式维护扫描线上的 s u m ( y ) sum(y) sum(y) . 同时, s u m ( y ) − s u m ( y − 1 ) sum(y)-sum(y-1) sum(y)sum(y1) 的值若为 1 1 1 ,则表明当前扫描线上 y y y 位置有一条直线。

但为了方便,还可以设一个 b o o l bool bool 型数组 E [ y ] E[y] E[y] ,来记录当前扫描线上 y y y 坐标是否有一条直线。

由于每个端点连接一条水平的边与一条竖直的边,因此对于当前扫描线上的一个待处理的端点 p ( x , y ) p(x,y) p(x,y),如果当前扫描线上 y y y 坐标上有一条垂直于 y y y 轴的边,则扫过此点后, y y y 位置就没有边了,即 p p p 是这条水平边的终点, s u m ( y ) sum(y) sum(y) 应减去 1 1 1 ;同理,如果当前扫描线的 y y y 位置上没有边,则扫过此点后,这个位置将有边,即 p p p 是一条新的水平边的起点, s u m ( y ) sum(y) sum(y) 应增加 1 1 1 .

接下来,如果多边形是合法的,则任意时刻扫描线上的点的个数 m m m 都应该是偶数,那么对于这 m m m 个点,会形成 m 2 \frac{m}{2} 2m 个竖直的边,且相邻的两个点成为同一条边的起点与终点。因此把扫描线上的点分为 m 2 \frac{m}{2} 2m 组处理。对于同一组内的两个点,这两个点构成的边不与其它水平的边有交点的充要条件是这两个点的 s u m sum sum 值相等,因此可以借此判断是否存在边的相交。

对于多边形中没有洞的判断,可以以任意顶点出发,判断是否所有顶点都可以沿着多边形的边到达该顶点,这可以使用并查集判断。而在此题中,题目保证了多边形没有洞,故无需检查。

最后,在扫描的过程中计算周长,并在如下几个地方判断多边形是否合法即可:

  1. 某一时刻,扫描线上有奇数个待处理的点。某一 x x x 坐标上有奇数个点就意味着扫描线上将存在奇数条边的,而合法的多边形任意 x x x 坐标上都应有偶数个端点与偶数条边。
  2. 除了多边形的极左端以左和极右端以右,在中间某一时刻,扫描线上出现了 0 0 0 条边。出现 0 0 0 条边意味着多边形断开了,这是不合法的。
  3. 某一时刻,扫描线上的两个点的 y y y 坐标相等,这意味着两个端点重合了。这个判断也可以放在排序后的离散化过程中。
  4. 在扫描线上待处理的点中,构成同一条竖直边的两个点的 s u m sum sum 值不相等。这意味着这两个点之间存在水平的边,在加入这个新的竖直的边后将会发生边的相交。
  5. 在扫描线扫描过多边形的极右端后, s u m sum sum 在任意位置都应为 0 0 0 . 如果某一位置不为 0 0 0 意味着这个多边形最终没有闭合。

 
 
下面贴代码:

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

typedef long long ll;
const int MAX_N = 100020;

struct P
{
	int x;
	int y;
	bool operator< (const P& ri)const
	{
		return x < ri.x || x == ri.x && y < ri.y;
	}
};
P p[MAX_N];
int kx[MAX_N];
int ky[MAX_N];

bool E[MAX_N];

int bit[MAX_N], _n;
void add(int i, const int x);
int sum(int i);

ll solve();

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

	while (T--)
	{
		printf("%lld\n", solve());
	}
	return 0;
}

void add(int i, const int x)
{
	i += 2;
	while (i < _n)
	{
		bit[i] += x;
		i += i & -i;
	}
}

int sum(int i)
{
	i += 2;
	int s = 0;
	while (i)
	{
		s += bit[i];
		i -= i & -i;
	}
	return s;
}

ll solve()
{
	static const int Invalid = -1;
	int n;
	scanf("%d", &n);

	int kxcnt = 0, kycnt = 0;
	for (int i = 0; i < n; ++i)
	{
		P& cur = p[i];
		scanf("%d%d", &cur.x, &cur.y);
		kx[kxcnt++] = cur.x;
		ky[kycnt++] = cur.y;
	}
	sort(p, p + n);
	sort(kx, kx + kxcnt);
	sort(ky, ky + kycnt);
	kxcnt = unique(kx, kx + kxcnt) - kx;
	kycnt = unique(ky, ky + kycnt) - ky;
	for (int i = 0; i < n; ++i)
	{
		P& cur = p[i];
		cur.x = lower_bound(kx, kx + kxcnt, cur.x) - kx;
		cur.y = lower_bound(ky, ky + kycnt, cur.y) - ky;
	}
	p[n].x = p[n].y = -1;

	_n = n + 4;
	memset(bit, 0, _n * sizeof(bit[0]));
	memset(E, 0, (n + 1) * sizeof(E[0]));

	ll res = 0;
	int lb = 0, rb = 0, ecnt = 0;
	for (int i = 0; i < kxcnt; ++i)
	{
		while (p[rb].x == i) ++rb;
		if ((rb - lb) & 1) return Invalid;

		if (i) res += (ll)ecnt * (kx[i] - kx[i - 1]);

		while (lb != rb)
		{
			const P& lo = p[lb++];
			const P& up = p[lb++];
			if (lo.y == up.y) return Invalid;

			int t1 = sum(lo.y);
			int t2 = sum(up.y - 1);
			if (t1 != t2) return Invalid;

			add(lo.y, E[lo.y] ? -1 : 1);
			add(up.y, E[up.y] ? -1 : 1);

			res += ky[up.y] - ky[lo.y];
			if (E[lo.y]) --ecnt;
			else ++ecnt;
			if (E[up.y]) --ecnt;
			else ++ecnt;

			E[lo.y] = !E[lo.y];
			E[up.y] = !E[up.y];
		}
		if (i + 1 < kxcnt && !ecnt) return Invalid;
	}

	for (int i = 0; i <= kycnt; ++i)
	{
		if (sum(i)) return Invalid;
	}
	return res;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值