现在的位置: 首页 > 综合 > 正文

扫描线算法在线段树中的应用

2013年04月08日 ⁄ 综合 ⁄ 共 3757字 ⁄ 字号 评论关闭

可能是没怎么搞计算几何的知识,至今对扫描线算法没有很清晰的认识,记得当初ZY大神是这样定义的:      

       每一个事件有一个开始时间和结束时间,将所有时间点排序,然后依次扫描,如果是开始时间就将时间加入,否则就将事件删除。则可以在任意时刻处理当前发生的事件。

扫描线在计算几何中使用相对多一些,我只是为了刷通线段树的列表粗略了了解了一下,只可就事论事,无法统筹全局。“矩形面积并”是扫描线与线段树结合的一个经典应用,同时也是区间离散化的一个经典例子。


矩形面积并平面上有N个矩形,各边均平行于坐标轴,求它们覆盖的总面积(重复覆盖的只计一次)。

解法:将矩形的竖边按照x坐标排序,每个矩形看做一个事件,矩形的左竖边看做事件的开始右竖边看做事件的结束,这样可以计算任意两条临边之间属于矩形并的面积(当前覆盖的纵坐标*横坐标差),过个图应该就可以理解了。。。

接下来就是区间离散化和维护统计量的过程了,离散化后横坐标有m个,如果建立m个节点那么线段树节点的长度无法计算(最简单的就是叶子节点,长度为0显然不对),因此线段树中建立m-1个叶子节点,第i个叶子节点代表i~i+1之间的距离(做到这里才知道当初对Poster那题的理解是一知半解的,不是半开半闭的问题而是节点保存什么信息的问题)。矩形面积并的线段树比较奇怪,虽然更新的是区间的值但是却不需要pushdown操作。而且更新自区间后也立即pushup(),线段树变化多端一定要灵活啊。。。


poj1151 Atlantis 
需离散化的浮点矩形面积比

import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Scanner;

public class Atlantis1151 {
	class SegTree {
		class node {
			int left, right, key;
			double sum;
			void init(int l, int r) {
				left = l;
				right = r;
				sum = 0;
				key = 0;
			}
			int mid() {
				return (left + right) >> 1;
			}
			double length() {
				return hash[right + 1] - hash[left];
			}
		}

		node tree[];

		SegTree(int maxn) {
			maxn = maxn * 3;
			tree = new node[maxn];
			for (int i = 1; i < maxn; i++)
				tree[i] = new node();
		}

		void init(int l, int r, int idx) {
			tree[idx].init(l, r);
			if (l == r)
				return;
			int mid = tree[idx].mid();
			init(l, mid, idx << 1);
			init(mid + 1, r, (idx << 1) | 1);
		}

		void update(int left, int right, int idx, int v) {
			if (tree[idx].left >= left && tree[idx].right <= right) {
				tree[idx].key += v;
				pushup(idx);
				return;
			}
			int mid = tree[idx].mid();
			if (left <= mid)
				update(left, right, idx << 1, v);
			if (right > mid)
				update(left, right, (idx << 1) | 1, v);
			pushup(idx);
		}

		void pushup(int idx) {
			if (tree[idx].key > 0)
				tree[idx].sum = tree[idx].length();
			else {
				if (tree[idx].left == tree[idx].right)
					tree[idx].sum = 0;
				else
					tree[idx].sum = tree[idx << 1].sum
							+ tree[(idx << 1) | 1].sum;
			}

		}
	}

	Scanner scan = new Scanner(System.in);

	class edge implements Comparable<edge> {
		int s, t, v;
		double x;
		public int compareTo(edge oth) {
			if (x < oth.x)
				return -1;
			if(x==oth.x&&v>oth.v)
				return -1;
			//在求矩形周长并时发现的错误,貌似面积并沒问题,也许是数据太弱的缘故,先写上,稍后研究
			return 1;
		}
	}

	class zone implements Comparable<zone> {
		int id;
		double h;
		public int compareTo(zone o) {
			if (h < o.h)
				return -1;
			return 1;
		}
	}

	SegTree st = new SegTree(310);
	edge[] ed = new edge[310];
	zone[] zn = new zone[310];
	int n, cnt;
	double hash[] = new double[310];

	void hash() {
		Arrays.sort(zn, 1, n * 2 + 1);
		cnt = 1;
		for (int i = 1; i <= 2 * n; i++) {
			if (i>1&&zn[i].h != zn[i - 1].h)
				cnt++;
			hash[cnt] = zn[i].h;
			int temp = zn[i].id;
			if (temp > 0)
				ed[temp].s = ed[temp + 1].s = cnt;
			else
				ed[-temp].t = ed[-temp + 1].t = cnt;
		}
	}

	void init() {
		for (int i = 0; i <= 300; i += 2) {
			ed[i] = new edge();
			ed[i + 1] = new edge();
			zn[i] = new zone();
			zn[i + 1] = new zone();
		}
	}

	void run() {
		int cas = 0;
		DecimalFormat df = new DecimalFormat("0.00");
		init();
		while (true) {
			cas++;
			n = scan.nextInt();
			if (n == 0)
				break;
			System.out.println("Test case #" + cas);
			for (int i = 1; i <= n * 2; i += 2) {
				ed[i].x = scan.nextDouble();
				ed[i].v = 1;
				zn[i].id = i;
				zn[i].h = scan.nextDouble();
				ed[i + 1].x = scan.nextDouble();
				ed[i + 1].v = -1;
				zn[i + 1].id = -i;
				zn[i + 1].h = scan.nextDouble();
			}
			hash();
			Arrays.sort(ed, 1, n * 2 + 1);
			st.init(1, cnt - 1, 1);
			st.update(ed[1].s, ed[1].t - 1, 1, 1);
			double ans = 0;
			for (int i = 2; i <= n * 2; i++) {
				ans += st.tree[1].sum * (ed[i].x - ed[i - 1].x);
				st.update(ed[i].s, ed[i].t - 1, 1, ed[i].v);
			}
			System.out.println("Total explored area: " + df.format(ans));
			System.out.println();
		}
	}

	public static void main(String[] args) {
		new Atlantis1151().run();
	}
}

Ps:很多大神离散化喜欢用二分做,具体的编码复杂度和事件复杂度有待进一步对比,但还是感觉写自己的方法比较顺手,因此暂时不改了。

矩形周长并:比面积并稍微复杂一些,横边和竖边要分别统计。贴个pushup()和统计代码,然后画张图应该就可以理解了(注意两条竖边重合的情况,poj1177discuss里有这种数据)

void pushup(int idx) {
			if (tree[idx].cnt > 0) {
				tree[idx].sum = tree[idx].length();//区间覆盖长度
				tree[idx].lp = tree[idx].rp = 1;//左右端点是否被覆盖
				tree[idx].num = 1;//区间内共有几个分散区间
			} else {
				if (tree[idx].left == tree[idx].right) {
					tree[idx].num = tree[idx].sum = 0;
					tree[idx].lp = tree[idx].rp = 0;
				} else {
					int l = idx << 1;
					int r = l + 1;
					tree[idx].lp = tree[l].lp;
					tree[idx].rp = tree[r].rp;
					tree[idx].sum = tree[l].sum + tree[r].sum;
					tree[idx].num = tree[l].num + tree[r].num
							- (tree[l].rp & tree[r].lp);
				}
			}
		}

for (int i = 1; i <= n * 2; i++) {
			if (i > 1)
				ans += st.tree[1].num* (ed[i].x - ed[i - 1].x) * 2;//横边
			st.update(ed[i].s, ed[i].t - 1, 1, ed[i].v);		
			ans += Math.abs(st.tree[1].sum - last);//竖边
			last = st.tree[1].sum;
		}


抱歉!评论已关闭.