POJ1151 Atlantis 扫描线算法
题目大意
给出几个矩形对角端点坐标,求这些矩形整体覆盖的面积。
扫描线算法
整个平面被每个矩形的水平边所在直线(以后简称“水平线”)分成了几个部分,而整体覆盖面积则为每相邻的两个水平线间夹的长度(以后简称“夹长”)以及水平线所对应的一个或几个矩形水平边(以后简称“水平线段”)在水平线中所占的长度之积的和。于是我们假设有一个扫描线从下往上扫。由排序得到夹长很容易,但是水平线段长就要用线段树了。
线段树
节点维护的是什么
水平线段在两个点[l,r]之间所占的长度CoverLen。
l,r是double怎么办?
运用离散化将double映射成整型排名。
注意事项
在把SortedData的初值全部设为0时,要考虑到l,r可能是0。这样在设置rank数组时,0所对应的排名就成了0,最后线段树爆栈导致RE。所以要让SortedData[0] = -1。
离散化后,如何体现水平线段原先的长度?
既然结点维护的格子是离散化后的排名,有了排名,原先的长度自然也就知道了。
线段树维护的是格子,而我们要维护的是点,怎么办?
把两点之间的区间作为线段树所维护的格子即可。线段树中格子p表示离散化后排名为p的点和p+1两个点之间的区间。(所以对线段树操作时,输入的r要-1)。
注意事项
为了使鲁棒性更高,防止出现“一个矩形就是一个竖直线”导致ar==al-1的情况,此时要及时返回。
包含于一段的区间的水平线段的个数CoverCnt不满足相加性,怎么办?
方法1:只在最底层节点查询具体值,上层节点只存标记
注意事项
判断是否PushDown要看该结点是否有DeltaTag标记,而一个结点是否在最底层这个标记无论是否有DeltaTag都要设置。
方法2:换一个定义
在一个结点中,如果一个结点所负责的区间是一个水平线段的子区间,且该结点的父节点不是水平线段的子区间,则该结点所负责的区间为该水平线段的极大子区间。我们用CoverCnt表示:满足该节点维护的区间是一个水平线段的极大子区间的水平线段的个数。如果该结点的CoverCnt>0,则该结点负责的区间必然全部覆盖;如果CoverCnt==0,则该节点内的水平线段长就等于两个子区间的水平线段长之和。特别地,如果该节点是叶子结点,则它所负责的区间内的水平线段长就是0。
根据此时CoverCnt的定义,我们不用(无法)PushDown。
注意事项:
如果要通过一个一个赋值来初始化线段树,循环终止条件不是N,要初始化就彻底一点。
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cassert> using namespace std; const int MAX_AREA = 110, MAX_LINE = MAX_AREA * 2, MAX_P = MAX_LINE * 2, MAX_NODE = MAX_P * 4; struct HorLine { double L, R, H; int Flag; HorLine(double l = 0, double r = 0, double h = 0, int flag = 0) :L(l), R(r), H(h), Flag(flag) {} bool operator < (const HorLine& a)const { return H < a.H; } }_lines[MAX_LINE]; struct Discret { private: double SortedData[MAX_P]; int Rank[MAX_P]; int N; int LowerBoundByVal(int l, int r, double k) { while (l < r) { int mid = (l + r) / 2; if (k <= SortedData[mid]) r = mid; else l = mid + 1; } return l; } int LowerBoundByRank(int l, int r, int k) { while (l < r) { int mid = (l + r) / 2; if (k <= Rank[mid]) r = mid; else l = mid + 1; } return l; } public: int RankCnt; void Init(double *a, int n) { N = n; memset(SortedData, 0, sizeof(SortedData)); memset(Rank, 0, sizeof(Rank)); for (int i = 1; i <= n; i++) SortedData[i] = a[i]; sort(SortedData + 1, SortedData + n + 1); SortedData[0] = -1; int curRank = 0; for (int i = 1; i <= n; i++) { if (SortedData[i] == SortedData[i - 1]) Rank[i] = curRank; else Rank[i] = ++curRank; } RankCnt = curRank; } int GetRankByVal(double val) { return Rank[LowerBoundByVal(1, N, val)]; } double GetValByRank(int rank) { return SortedData[LowerBoundByRank(1, N, rank)]; } }g; struct RangeTree { private: int N; struct Node { Discret *Dis; int CoverCnt; double CoverLen; double Len; Node():Len(-1){} void GetLen(int l, int r) { if(Len < 0) Len = Dis->GetValByRank(r + 1) - Dis->GetValByRank(l); } }_nodes[MAX_NODE]; void UpdateNode(int cur, int l, int r, int delta) { _nodes[cur].GetLen(l, r); _nodes[cur].CoverCnt += delta; if (_nodes[cur].CoverCnt > 0) _nodes[cur].CoverLen = _nodes[cur].Len; else if (l == r) _nodes[cur].CoverLen = 0; else _nodes[cur].CoverLen = _nodes[cur * 2].CoverLen + _nodes[cur * 2 + 1].CoverLen; } void Update(int cur, int sl, int sr, int al, int ar, int delta) { if (al <= sl && sr <= ar) { UpdateNode(cur, sl, sr, delta); return; } int mid = (sl + sr) / 2; if (al <= mid) Update(cur * 2, sl, mid, al, ar, delta); if (ar > mid) Update(cur * 2 + 1, mid + 1, sr, al, ar, delta); UpdateNode(cur, sl, sr, 0); } double Query(int cur, int sl, int sr, int al, int ar) { if (al <= sl && sr <= ar) return _nodes[cur].CoverLen; int mid = (sl + sr) / 2; double ans = 0; if (al <= mid) ans += Query(cur * 2, sl, mid, al, ar); if (ar > mid) ans += Query(cur * 2, mid + 1, sr, al, ar); return ans; } public: RangeTree(Discret* dis) { for (int i = 1; i <= MAX_NODE - 1; i++) _nodes[i].Dis = dis; } void Init(int n) { N = n; for (int i = 1; i <= MAX_NODE - 1; i++) _nodes[i].CoverCnt = 0, _nodes[i].CoverLen = 0, _nodes[i].Len = -1; } void Update(int l, int r, int delta) { if (r < l) return; Update(1, 1, N, l, r, delta); } double Query(int l, int r) { if (r < l) return 0; return Query(1, 1, N, l, r); } }h(&g); int main() { static double pExist[MAX_LINE * 2]; int AreaCnt; int caseCnt = 0; while (~scanf("%d", &AreaCnt) && AreaCnt) { memset(pExist, 0, sizeof(pExist)); memset(_lines, 0, sizeof(_lines)); for (int i = 1; i <= AreaCnt; i++) { double x1, x2, y1, y2; scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2); if (y1 < y2) swap(y1, y2); _lines[i * 2 - 1] = HorLine(x1, x2, y1, -1); _lines[i * 2] = HorLine(x1, x2, y2, 1); pExist[i * 2 - 1] = x1; pExist[i * 2] = x2; } sort(_lines + 1, _lines + AreaCnt * 2 + 1); g.Init(pExist, AreaCnt * 2); h.Init(g.RankCnt); double ans = 0; for (int i = 1; i <= AreaCnt * 2; i++) { ans += h.Query(1, g.RankCnt) * (_lines[i].H - _lines[i - 1].H); h.Update(g.GetRankByVal(_lines[i].L), g.GetRankByVal(_lines[i].R) - 1, _lines[i].Flag); } printf("Test case #%d\nTotal explored area: %.2lf\n\n", ++caseCnt, ans); } }