可能是沒怎麼搞計算幾何的知識,至今對掃描線演算法沒有很清晰的認識,記得當初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;elsetree[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;elseed[-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;}