可能是没怎么搞计算几何的知识,至今对扫描线算法没有很清晰的认识,记得当初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; }