`

线段树-poj1177-N个矩形求边长(离散化+扫描线)

阅读更多
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--;
                }
            }
        }
    }
    
}
1
1
分享到:
评论

相关推荐

    POJ2528-Mayor's posters【区间映射压缩(离散化)+线段树】

    POJ2528-Mayor's posters 【区间映射压缩(离散化)+线段树】 解题报告+AC代码 http://hi.csdn.net/!s/83XZJU ========&gt; 我的所有POJ解题报告链接: http://blog.csdn.net/lyy289065406/article/details/6642573

    线段树练习POJ 3264

    线段树的基本思想是将数组或序列分成多个部分(通常是二分),每个部分对应线段树的一个节点,通过节点间的合并操作,快速获取区间信息。 线段树的构建通常分为两个阶段:初始化和构造。初始化时,我们将原始数组中...

    POJ3277.rar_3277 poj_poj3277_多个面积_线段树

    标题中的“POJ3277.rar_3277 poj_poj3277_多个面积_线段树”是指一个与编程竞赛相关的压缩文件,特别提到了POJ(Problemset Online Judge)的第3277号问题。POJ是一个著名的在线编程竞赛平台,参与者需要解决各种...

    POJ3253-POJ3253-Fence Repair【STL优先队列】

    标题“POJ3253-POJ3253-Fence Repair【STL优先队列】”指的是一个在线编程竞赛题目,源自北京大学的在线判题系统POJ(Problem Online Judge)。该题目要求参赛者使用C++编程语言解决特定的问题,并且在解决方案中...

    POJ 分类题目

    ### POJ 分类题目知识点详解 #### 一、基本算法 **1. 枚举** - **定义**:枚举是一种通过尝试所有可能情况来解决问题的方法。 - **示例题目**: - poj1753 - poj2965 - **应用场景**:适用于问题规模较小或解决...

    ACM-POJ 算法训练指南

    1. **高级数据结构**:如树状数组、线段树(poj2528, poj2828, poj2777, poj2886, poj2750)。 2. **平衡树**:如AVL树、红黑树等(poj2482, poj2352)。 3. **并查集**:用于处理不相交集合的问题(poj1195, poj...

    经典 的POJ 分类

    根据给定的信息,我们可以将这些知识点分为几个大类别:数据结构、图论、搜索算法、动态规划、数学问题以及字符串处理等。 ### 数据结构 #### 队列 - **题目示例**: - POJ 3295:考察队列的基本应用。 #### ...

    poj 2352 stars(树状数组,线段树)

    ### POJ 2352 Stars - 树状数组与线段树实现 #### 题目背景 POJ 2352 Stars 是一道经典的算法题目,它提供了使用树状数组或线段树来解决的可能性。这两种数据结构都是为了高效处理区间查询问题而设计的。在这篇...

    线段树入门学习(二)(兼解POJ3468) JAVA

    POJ3468是一个典型的线段树应用题目,它要求对一组数进行多次区间加法操作,然后查询区间之和。线段树在这里可以高效地处理这些操作,避免了每次修改或查询时都遍历整个数组。 在Main.java中,我们可能会看到以下...

    poj(PKU-2314-POJ language

    根据提供的文件信息,我们可以分析出该段代码是用于解决POJ平台上的2314题的一种解决方案,主要涉及到了变量管理、表达式处理等方面。下面将详细解释代码中的关键概念和实现逻辑。 ### 关键概念解析 #### Variable...

    POJ2002-Squares

    【标题】"POJ2002-Squares"是一个经典的计算机编程题目,源自北京大学的在线判题系统(POJ,即PKU Online Judge)。这个题目主要涉及到算法设计和实现,尤其是数学和动态规划方面的知识。 【描述】"解题报告+AC代码...

    线段树入门学习(三)懒操作(兼解POJ1823) JAVA

    在本篇博文中,我们将深入探讨线段树的概念,以及如何实现懒惰更新(Lazy Propagation)策略,同时结合POJ1823问题进行实战应用。懒惰更新是优化线段树性能的关键技巧,它避免了频繁地对每个节点进行更新,从而减少...

    线段树题目

    - **POJ 1151**:结合了线段树和离散化的技巧来求解矩形面积的并集。这需要对输入的坐标进行离散化处理,以减少空间复杂度,并使用线段树维护区间信息。 - **POJ 1177**:求解矩形周长的并集。此题与POJ 1151类似,...

    acm新手训练方案新手必备

    - **高级数据结构**:例如线段树、树状数组、差分约束系统等。 - **字符串算法**:包括后缀数组、AC自动机等。 - **组合优化**:涉及组合优化问题的求解方法。 - **数学建模**:学习如何将实际问题转化为数学模型...

    poj2823.cpp.tar.gz_线段树

    在`poj2823.cpp`源代码中,我们可以看到实现线段树的具体细节,包括如何初始化、更新以及查询线段树。此外,代码可能还包括了问题的输入输出处理,错误检查,以及可能的优化策略,比如lazy propagation(惰性传播)...

    ACM题目分类.txt

    - **描述**:几何算法涉及点、线、面等几何对象的处理。 - **应用场景**:计算机图形学、地图信息系统等。 - **相关题目**: - POJ 3273 - POJ 3258 - POJ 1905 - POJ 3122 以上是对给定文件中提及的各种算法...

    Super-Memo-poj3580.zip_memo

    标题中的"Super-Memo-poj3580.zip_memo"暗示了这是一个关于Super-Memo算法的编程解决方案,可能是为了解决某个特定问题,比如在POJ(Programming Online Judge)平台上提交的第3580题。Splay Trees是数据结构领域中...

    leetcodeoj调试-My-POJ:我的POJ

    《深入探索LeetCode OJ与My-POJ调试实践》 在编程的世界里,LeetCode OJ(在线判题系统)和My-POJ(个人POJ)是程序员提升技能、锻炼算法的重要平台。这两个工具提供了丰富的编程挑战,帮助开发者在解决实际问题中...

    线段树例题(唐文斌).pdf

    ### 线段树例题解析 #### Brackets (SPOJ61, BRCKTS) **题目描述:** 此题要求我们维护一个长度为 \(N\) 的小括号序列 \(A\),并支持两种操作: 1. **Replace(i)**:将第 \(i\) 个位置的括号方向反转。 2. **Check...

    西北工业大学-c语言-POJ-题目及答案-第七季.doc

    西北工业大学 C 语言 POJ 题目及答案第七季 该文档提供了西北工业大学 POJ(Programming Online Judge)平台上的 C 语言编程题目及答案,共计 7 题,涵盖了基本数据类型、运算符、控制结构、函数和数组等 C 语言...

Global site tag (gtag.js) - Google Analytics