线段树求多边形周长~~~扫描线大法好,线段树真的厉害

我有一颗线段树,我有一组扫描线,线段树,扫描线,线段树扫描线,扫描线线段树。

                                                                                       --盗版-PPAP-OTZ发明这个的大佬

接下来一段是蒟蒻的思考,请学扫描线的童鞋自觉跳过,不过如果没人更早题出这种算法,请允许我保留命名权(一脸滑稽)

在学开始扫描线的时候。我觉得扫描线完全可以用前向星去更新,head[u]->line[1]->line[2]->...插入应该线段有两种情况从line【1】开始扫,碰到有交集的就和起来,并去掉原line,最后线段树访问所有根节点的前向星《树式前向星》,感觉自己有了什么大发现。p.s.这样应该是可以过这个题(p o j-1177)的。面积更新也是可以的,加lazy标记,储存一个前向星,然后有必要时向下传递也是可以的,如果没有扫描线算法,我多半是个可以写个论文发表了。《空间复杂度高了个常数,速度上应该也差不多》

进入正题

扫描线求这种周长其实有两种策略

一种是只用一颗线段树的

扫描线是要存在的,所以我们用一个类Line储存,每个Line储存他的横坐标,两个端点的纵坐标,和矩形出入口属性。因为他要排序重载<操作符或写一个继承一个Comparable,使它具有排序能力。这是为了保证每一个后插入的边都有在下一个边的右边(不是严格的右,包括它本身的),这样需要去重的部分就只有x相同的地方了。x不同的时候就是线段树的区间,求区间长度有没有覆盖过(这里的numseg表示覆盖过的个数),对每个区间更新一个flag值《+1,-1代表加一个或减一个边》即可,只要不是1就可以加上去,所以可以构造出。然后横向的边就是这个线和上一个线的x的差值。

线段树各个非叶子节点是长度的和,叶子节点是这个点进入的次数

各种值的定义后面都有说明,这里不兹论述,请见代码。

离散化不是这里的重点,简单概述一下,会的跳过。离散化实际上应该是连续化,是把离散的点边的连续的方法。可以大大减少线段树的区间总长度,减少占地空间,加快运算。而通过离散的值《在数组里就是下标》又很容易和原值对应《下标和值对应天经地义好吧》,代码里面有个去重函数,排序加去重全用c++标准程式库就好了。就这了。

通过插入两条边时的情况让大家理解线段树的作用,显然,x2-x1下面line2上的线段被重复计算了,而题意要去去掉额外的轮廓线,所以我们要想办法去掉它,线段树的价值


这时候线段树救发挥功效了

不妨假设line1的上下端点为2,0,line2的上下端点为1,3


//代码里的y数组应该叫x,这里忽略不计谢谢

#include<iostream>
#include<algorithm>
using namespace std;
const int MAXN = 10010;
class Tree {
	//纵向扫描线
private:
	class Line {
	public:
		int x;
		int y1;//顶点
		int y2;//底点
		int flag;//矩形左侧边表示进入矩形,标记为1,出矩形为-1
		bool operator < (const Line t) const {
			return x < t.x;
		}
		void set(int x, int y1, int y2, int f){
			this->x = x;
			this->y1 = y1;
			this->y2 = y2;
			this->flag = f;
		}
		Line() {}
	};
	class Node {
	public:
		int l, r;
		int cnt;//有效长度
		int lf, rf;//实际的左右端点
		int numseg;//分支数,一个分支对应两条竖线
		int c;//记录覆盖情况
		bool lcover, rcover;
		void set(int l, int r, int lf, int rf) {
			this->l = l;
			this->r = r;
			this->rf = rf;
			this->lf = lf;
			this->cnt = 0;
			this->c = 0;
			this->numseg = 0;
			this->c = 0;
			this->lcover = this->rcover = false;
		}
		Node() {}
	};
	Node segTree[MAXN << 2];
	Line line[MAXN];
	int y[MAXN];
	void build(int i, int l, int r) {
		segTree[i].set(l, r, y[l], y[r]);
		if (l + 1 == r)return;
		int mid = (l + r) >> 1;
		build(i << 1, l, mid);
		build((i << 1) | 1, mid, r);
	}
	void calen(int i){
		if (segTree[i].c>0){
			segTree[i].cnt = segTree[i].rf - segTree[i].lf;
			segTree[i].numseg = 1;
			segTree[i].lcover = segTree[i].rcover = true;
			return;
		}
		if (segTree[i].l + 1 == segTree[i].r){
			segTree[i].cnt = 0;
			segTree[i].numseg = 0;
			segTree[i].lcover = segTree[i].rcover = false;
		}
		else{
			segTree[i].cnt = segTree[i << 1].cnt + segTree[(i << 1) | 1].cnt;
			segTree[i].lcover = segTree[i << 1].lcover;
			segTree[i].rcover = segTree[(i << 1) | 1].rcover;
			segTree[i].numseg = segTree[i << 1].numseg + segTree[(i << 1) | 1].numseg;
			if (segTree[i << 1].rcover&&segTree[(i << 1) | 1].lcover)segTree[i].numseg--;
		}
	}
	void update(int i, Line e) {
		if (segTree[i].lf == e.y1&&segTree[i].rf == e.y2) {
			segTree[i].c += e.flag;
			calen(i);
			return;
		}
		if (e.y2 <= segTree[i << 1].rf)
			update(i << 1, e);
		else if (e.y1 >= segTree[(i << 1) | 1].lf)
			update(i << 1 | 1, e);
		else {
			Line temp = e;
			temp.y2 = segTree[i << 1].rf;
			update(i << 1, temp);
			temp = e;
			temp.y1 = segTree[i << 1 | 1].lf;
			update(i << 1 | 1, temp);
		}
		calen(i);
	}
	int work() {
		int ans = 0;
		int last = 0;
		for (int i = 0; i<t - 1; i++){
			update(1, line[i]);
			ans += segTree[1].numseg * 2 * (line[i + 1].x
				- line[i].x);
			ans += abs(segTree[1].cnt - last);
			last = segTree[1].cnt;
		}
		update(1, line[t - 1]);
		ans += abs(segTree[1].cnt - last);
		return ans;
	}
	int t;
public:
	Tree() {}
	void build(int n) {
		int x1, x2, y1, y2;
		t = 0;
		for (int i = 0; i < n; i++) {
			cin >> x1 >> y1 >> x2 >> y2;
			line[t].set(y1, x1, x2, 1);
			y[t++] = x1;
			line[t].set(y2, x1, x2, -1);
			y[t++] = x2;
		}
		sort(line, line + t);
		sort(y, y + t);
		int m = unique(y, y + t) - y;
		build(1, 0, m - 1);
	}
	void printAns() {
		cout << work() << endl;
	}
	~Tree() {
		free(segTree);
		free(y);
		free(line);
	}
};
Tree *tree;
int main() {
	int n;
	while (cin >> n) {
		tree = new Tree();
		tree->build(n);
		tree->printAns();
		free(tree);
	}
}
/*
	*都是腰间盘,你为何如此突出
	*It's all the waist disc, why are you so protruding
*/


一种是横竖各一个线段树,分别储存纵边和横边。这种比较简单,就讲下思路,线段树上确定的子节点上,给这个区间上加边的数量和长度,每次加入一个新的边然后把重的地方重算,就好了。

猜你喜欢

转载自blog.csdn.net/qq_41104612/article/details/80383396
今日推荐