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--;
}
}
}
}
}
线段树-poj1177-N个矩形求边长(离散化+扫描线)
最新推荐文章于 2020-06-08 17:07:48 发布