package com.ljn.base;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
/**
* POJ 1177 (线段树+离散化+扫描线),题目链接为http://poj.org/problem?id=1177
*
* 关于扫描线(ScanLine)和线段树结点(Node)各字段的定义,请参看陈宏的论文:
* 《数据结构的选择与算法效率——从IOI98试题PICTURE谈起》链接为http://wenku.baidu.com/view/1856cf84b9d528ea81c77918.html
* 强烈建议将上面的论文看一遍,因为各字段的定义和计算不好理解
*
* 个人理解:
* 离散化:
* 例如有如下三条线段:(-100,-90)(10,20)(3333,4444)
* 如果建立的线段树是Tree(-100, 4444)那会浪费相当多的空间
* 所谓离散化就是把这三条线段映射成:(0,1)(2,3)(4,5)建立的线段树为Tree(0,5)
* 插入和删除时要注意把真实值映射成离散值再操作树
* 本题在解题时还要注意把数组y的值去重
*
* 轮廓周长计算方法:
* 有两种
* 1.先扫描竖边,再扫描横边,两者相加
* 2.只扫描竖边,一边扫描一边加上横边。扫描时前后两条扫描边在x轴的距离则为横边的长度
* 下面的代码是按第2种方法来执行的
*
* 采用上面的第2种方法时,横边的计算上面讲了,那竖边怎么办呢?
* 因为前后两条扫描线是有可能有重叠的,因此周长的增量只是这两条线在y轴上的长度差(|Tree1.M - Tree2.M|)
*
* 1.下面的代码用数组来保存线段树
* 用链表会更用更少的空间,有空再写一个
* 2.下面的线段树的叶子结点为[i,i+1],而不是[i,i]
*
* @author lijinnan
*/
public class SegmentTreePOJ1177 {
//扫描线(竖线)
class ScanLine {
int x; //x坐标
int y1; //顶点的y坐标
int y2; //底端点的y坐标
int flag; //1=入边,0=出边。从左到右扫描时,矩形的左边称为入边,右边称为出边
}
//线段树的结点,也就是一条线段
class Node {
int left; //线段的左值
int right; //线段的右值
int count; //线段出现的次数
int line; //线段跨越了几个段。如对于Node[1,5],三个子结点[1,2],[2,3],[4,5]线段被覆盖,则line=2,因为 [1,2],[2,3]是连续的
int lbd; //左值是否出现(覆盖)过
int rbd; //右值是否出现(覆盖)过
int m; //以本结点为根结点的子树代表的长度
}
Node[] node;
ScanLine[] scan;
int[] y;
public static void main(String[] args) {
//左下方和右上方的两个点(x1,y1)(x2,y2)定义了一个矩形。共7个矩形,14个点
int[][] input = {
{ -15, 0, 5, 10, },
{ -5, 8, 20, 25, },
{ 15, -4, 24, 14, },
{ 0, -6, 16, 4, },
{ 2, 15, 10, 22, },
{ 30, 10, 36, 20, },
{ 34, 0, 40, 16, },
};
new SegmentTreePOJ1177().process(input);
}
public void process(int[][] input) {
init(input.length);
//读入并保存扫描线
int i = 0;
for (int[] segment : input) {
scan[i].x = segment[0];
scan[i].y1 = segment[1];
scan[i].y2 = segment[3];
scan[i].flag = 1;
y[i] = segment[1];
i++;
scan[i].x = segment[2];
scan[i].y1 = segment[1];
scan[i].y2 = segment[3];
scan[i].flag = 0;
y[i] = segment[3];
i++;
}
//将扫描线按x坐标的大小从左到右排序;x坐标相等的,入边在前,出边在后
Arrays.sort(scan, new Comparator<ScanLine>() {
public int compare(ScanLine line1, ScanLine line2) {
if (line1.x == line2.x) {
return line1.flag - line2.flag;
}
return line1.x - line2.x;
}
});
//得到离散化后的值再建线段树
Set<Integer> set = sortAndMakeUnique(y);
int N = set.size();
y = new int[N];
int j = 0;
for(Integer ele : set) {
y[j++] = ele;
}
build(0, N - 1, 0);
int perimeter = 0;
int now_m = 0;
int now_line = 0;
for(int k = 0, len = scan.length; k < len; k++) {
if (scan[k].flag == 1) {
insert(scan[k].y1, scan[k].y2, 0);
} else {
remove(scan[k].y1, scan[k].y2, 0);
}
if (k >= 1) {
perimeter += 2 * now_line * (scan[k].x - scan[k - 1].x); //加上两条横边
}
perimeter += Math.abs(node[0].m - now_m); //加上竖边
now_m = node[0].m;
now_line = node[0].line;
}
System.out.println("perimeter = " + perimeter); //expected:228
}
/**
* 初始化
* @param rectangleCount 矩形的个数
* 竖边(扫描线)的条数为rectangleCount * 2
* y轴坐标的坐标值也是rectangleCount * 2
*/
public void init(int rectangleCount) {
int N = rectangleCount * 2;
int nodeCount = getNodeCount(N);
node = new Node[nodeCount];
for(int i = 0, len = node.length; i < len; i++) {
node[i] = new Node();
}
scan = new ScanLine[N];
for(int i = 0, len = scan.length; i < len; i++) {
scan[i] = new ScanLine();
}
y = new int[N];
}
/**
* 求得线段树的结点个数
* 线段树用数组存储,若表示的范围是[1,n]那么这个完全二叉树的高度为h=ceil(log2N)
* 结点数为:1 + 2 + 4 + 8 + ... + 2^h (根结点为第0层)
*/
public int getNodeCount(int n) {
int result = 1;
int base = 1;
int height = (int) Math.ceil(Math.log(n) / Math.log(2));
for(int i = 1; i <= height; i++) {
base *= 2;
result += base;
}
return result;
}
//Set可去重,TreeSet可排序
private Set<Integer> sortAndMakeUnique(int[] y) {
Set<Integer> set = new TreeSet<Integer>();
for (int i : y) {
Integer j = Integer.valueOf(i);
set.add(j);
}
return set;
}
public void build(int left, int right, int i) {
node[i].left = left;
node[i].right = right;
node[i].lbd = 0;
node[i].rbd = 0;
node[i].count = 0;
node[i].line = 0;
node[i].m = 0;
if (right - left > 1) {
int mid = left + (right - left) / 2;
build(left, mid, 2 * i + 1);
build(mid, right, 2 * i + 2);
}
}
public void insert(int left, int right, int i) {
if (y[node[i].left] == left && y[node[i].right] == right) {
node[i].count++;
} else {
if (node[i].right - node[i].left == 1) {
return;
} else{
int mid = node[i].left + (node[i].right - node[i].left) / 2;
if (right <= y[mid]) {
insert(left, right, 2 * i + 1);
} else if (left >= y[mid]){
insert(left, right, 2 * i + 2);
} else {
insert(left, y[mid], 2 * i + 1);
insert(y[mid], right, 2 * i + 2);
}
}
}
update_m(i);
update_line(i);
}
public void remove(int left, int right, int i) {
if (y[node[i].left] == left && y[node[i].right] == right) {
node[i].count--;
} else {
if (node[i].right - node[i].left == 1) {
return;
} else{
int mid = node[i].left + (node[i].right - node[i].left) / 2;
if (right <= y[mid]) {
remove(left, right, 2 * i + 1);
} else if (left >= y[mid]) {
remove(left, right, 2 * i + 2);
} else {
remove(left, y[mid], 2 * i + 1);
remove(y[mid], right, 2 * i + 2);
}
}
}
update_m(i);
update_line(i);
}
public void update_m(int i) {
if (node[i].count > 0) {
node[i].m = y[node[i].right] - y[node[i].left];
} else {
if (node[i].right - node[i].left == 1) {
node[i].m = 0;
} else {
node[i].m = node[2 * i + 1].m + node[2 * i + 2].m;
}
}
}
public void update_line(int i) {
if (node[i].count > 0) {
node[i].lbd = 1;
node[i].rbd = 1;
node[i].line = 1;
} else {
if (node[i].right - node[i].left == 1) {
node[i].lbd = 0;
node[i].rbd = 0;
node[i].line = 0;
} else {
node[i].lbd = node[2 * i + 1].lbd;
node[i].rbd = node[2 * i + 2].lbd;
node[i].line = node[2 * i + 1].line + node[2 * i + 2].line;
if (node[2 * i + 1].rbd == 1 && node[2 * i + 2].lbd == 1) { //左右孩子结点刚好连续上了,则跨越的段数要减1
node[i].line--;
}
}
}
}
}
分享到:
相关推荐
POJ2528-Mayor's posters 【区间映射压缩(离散化)+线段树】 解题报告+AC代码 http://hi.csdn.net/!s/83XZJU ========> 我的所有POJ解题报告链接: http://blog.csdn.net/lyy289065406/article/details/6642573
线段树的基本思想是将数组或序列分成多个部分(通常是二分),每个部分对应线段树的一个节点,通过节点间的合并操作,快速获取区间信息。 线段树的构建通常分为两个阶段:初始化和构造。初始化时,我们将原始数组中...
标题中的“POJ3277.rar_3277 poj_poj3277_多个面积_线段树”是指一个与编程竞赛相关的压缩文件,特别提到了POJ(Problemset Online Judge)的第3277号问题。POJ是一个著名的在线编程竞赛平台,参与者需要解决各种...
标题“POJ3253-POJ3253-Fence Repair【STL优先队列】”指的是一个在线编程竞赛题目,源自北京大学的在线判题系统POJ(Problem Online Judge)。该题目要求参赛者使用C++编程语言解决特定的问题,并且在解决方案中...
### POJ 分类题目知识点详解 #### 一、基本算法 **1. 枚举** - **定义**:枚举是一种通过尝试所有可能情况来解决问题的方法。 - **示例题目**: - poj1753 - poj2965 - **应用场景**:适用于问题规模较小或解决...
根据给定的信息,我们可以将这些知识点分为几个大类别:数据结构、图论、搜索算法、动态规划、数学问题以及字符串处理等。 ### 数据结构 #### 队列 - **题目示例**: - POJ 3295:考察队列的基本应用。 #### ...
### POJ 2352 Stars - 树状数组与线段树实现 #### 题目背景 POJ 2352 Stars 是一道经典的算法题目,它提供了使用树状数组或线段树来解决的可能性。这两种数据结构都是为了高效处理区间查询问题而设计的。在这篇...
1. **高级数据结构**:如树状数组、线段树(poj2528, poj2828, poj2777, poj2886, poj2750)。 2. **平衡树**:如AVL树、红黑树等(poj2482, poj2352)。 3. **并查集**:用于处理不相交集合的问题(poj1195, poj...
POJ3468是一个典型的线段树应用题目,它要求对一组数进行多次区间加法操作,然后查询区间之和。线段树在这里可以高效地处理这些操作,避免了每次修改或查询时都遍历整个数组。 在Main.java中,我们可能会看到以下...
根据提供的文件信息,我们可以分析出该段代码是用于解决POJ平台上的2314题的一种解决方案,主要涉及到了变量管理、表达式处理等方面。下面将详细解释代码中的关键概念和实现逻辑。 ### 关键概念解析 #### Variable...
【标题】"POJ2002-Squares"是一个经典的计算机编程题目,源自北京大学的在线判题系统(POJ,即PKU Online Judge)。这个题目主要涉及到算法设计和实现,尤其是数学和动态规划方面的知识。 【描述】"解题报告+AC代码...
在本篇博文中,我们将深入探讨线段树的概念,以及如何实现懒惰更新(Lazy Propagation)策略,同时结合POJ1823问题进行实战应用。懒惰更新是优化线段树性能的关键技巧,它避免了频繁地对每个节点进行更新,从而减少...
- **POJ 1151**:结合了线段树和离散化的技巧来求解矩形面积的并集。这需要对输入的坐标进行离散化处理,以减少空间复杂度,并使用线段树维护区间信息。 - **POJ 1177**:求解矩形周长的并集。此题与POJ 1151类似,...
- **高级数据结构**:例如线段树、树状数组、差分约束系统等。 - **字符串算法**:包括后缀数组、AC自动机等。 - **组合优化**:涉及组合优化问题的求解方法。 - **数学建模**:学习如何将实际问题转化为数学模型...
在`poj2823.cpp`源代码中,我们可以看到实现线段树的具体细节,包括如何初始化、更新以及查询线段树。此外,代码可能还包括了问题的输入输出处理,错误检查,以及可能的优化策略,比如lazy propagation(惰性传播)...
- **描述**:几何算法涉及点、线、面等几何对象的处理。 - **应用场景**:计算机图形学、地图信息系统等。 - **相关题目**: - POJ 3273 - POJ 3258 - POJ 1905 - POJ 3122 以上是对给定文件中提及的各种算法...
标题中的"Super-Memo-poj3580.zip_memo"暗示了这是一个关于Super-Memo算法的编程解决方案,可能是为了解决某个特定问题,比如在POJ(Programming Online Judge)平台上提交的第3580题。Splay Trees是数据结构领域中...
《深入探索LeetCode OJ与My-POJ调试实践》 在编程的世界里,LeetCode OJ(在线判题系统)和My-POJ(个人POJ)是程序员提升技能、锻炼算法的重要平台。这两个工具提供了丰富的编程挑战,帮助开发者在解决实际问题中...
西北工业大学 C 语言 POJ 题目及答案第七季 该文档提供了西北工业大学 POJ(Programming Online Judge)平台上的 C 语言编程题目及答案,共计 7 题,涵盖了基本数据类型、运算符、控制结构、函数和数组等 C 语言...