自由堆叠的屋顶——线段树的应用

自由堆叠的屋顶

时间限制(普通/Java) : 1000 MS/ 3000 MS          运行内存限制 : 65536 KByte
总提交 : 109            测试通过 : 39 

比赛描述

sed 同学最近突发奇想,认为伟大的建筑物的屋顶应该是“自由堆叠”出来的,他的设计方案是:将各种颜色的长方形建筑板材堆叠在一起,并保证各个板材长边、宽边均相互平行或在一条直线上,板材之间的重叠部分用连接装置固定在一起。

你的任务是计算这个“自由堆叠的屋顶”所覆盖的面积。sed 将会在屋顶平面上建立一个二维坐标系,提供给你每个长方形建筑板材左上角、右下角的坐标。为简化计算,这里忽略板材的厚度,假设它们都在同一个平面上。



输入

输入数据包含多组测试案例。

每组测试案例由N(0≤N≤100)开头,后续N行每行包含4个实数x1;y1;x2;y2 (0 <= x1 < x2 <= 100000建筑单位;0 <= y1 < y2 <= 100000建筑单位)。

(x1; y1) 、 (x2;y2)分别是长方形建筑板材左上角、右下角的坐标。

单独一个行输入0表示输入结束,无需处理。

输出

对于每个测试用例,输出以下信息: 第1行形如“Build #k”,这里k是测试用例序号(以1开始),第二行形如“Total area: a”,a是总覆盖面积,保留两位小数。

样例输入

2
10 10 20 20
15 15 25 25.5
1
10 10 20 20
0

样例输出

Build #1
Total explored area: 180.00
Build #2
Total explored area: 100.00

题目来源

南京邮电大学计算机学院首届ACM程序设计大赛(2009)


我的答案(Java):(WrongAnswerAtTest1 不知道问题出在哪儿

import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Scanner;

public class Main{
	
	
	public final int MAXN=100;
	public int N;// 板块个数
	public float[] position=new float[MAXN*4];// 板块坐标
	
	public float[] X;// 保存线段的横坐标
	public Line[] lines;// 保存线段
	public Node[] segTree=new Node[MAXN*8];
	
	public void input(Scanner sc) throws Exception{
		try {
			// 根据输入的n来创建数组
//			int posNum = 4 * N;
//			position = new float[posNum];

			for (int i = 0; i < N; i++) {
				float x1 = sc.nextFloat();
				float y1 = sc.nextFloat();
				float x2 = sc.nextFloat();
				float y2 = sc.nextFloat();

				if (!checkXY(x1, y1, x2, y2))
					throw new Exception("坐标不合法");

				position[i * 4 + 0] = x1;
				position[i * 4 + 1] = y1;
				position[i * 4 + 2] = x2;
				position[i * 4 + 3] = y2;
			}

		} catch (Exception e) {
			throw e;
		}
	}
	/**
	 * 检查坐标是否合法
	 * @param x1
	 * @param y1
	 * @param x2
	 * @param y2
	 * @return
	 */
	public boolean checkXY(float x1,float y1,float x2,float y2 ){
		if (check(x1) && check(x2) && check(y1) && check(y2) && x1 < x2
				&& y1 < y2)
			return true;
		return false;
	}
	private boolean check(float x){
		if(x<0||x>100000)
			return false;
		return true;
	}
	/**
	 * 将矩形的横边转化为线段,按照y值排序;同时将横坐标排序去重
	 * @return
	 */
	public void transfer(){
	
		lines=new Line[N*2];
		X=new float[N*2];
		
		for(int i=0;i<N;i++){
			float x1=position[i*4+0];
			float y1=position[i*4+1];
			float x2=position[i*4+2];
			float y2=position[i*4+3];
			
			Line line1=new Line(x1, x2, y1);
			line1.seq=1;// 下边
			Line line2=new Line(x1, x2, y2);
			line2.seq=-1;// 上边
			
			lines[i*2]=line1;
			lines[i*2+1]=line2;
			
			X[i*2]=x1;
			X[i*2+1]=x2;
		}
	}
	
	/**
	 * 线段排序
	 * @param lines
	 */
	public void sort(Line[] lines,int fromIndex,int toIndex){
		Arrays.sort(lines,fromIndex,toIndex, new Comparator<Line>() {
			@Override
			public int compare(Line o1, Line o2) {
				float f=o1.y-o2.y;
				if(f>0)
					return 1;
				else if(f<0)
					return -1;
				else 
					return 0;
			}
		});
	}
	public void sort(float[] X,int fromIndex,int toIndex){
		Arrays.sort(X,fromIndex,toIndex);
	}
	public int search(float[] X,int fromIndex,int toIndex,float x){
		return Arrays.binarySearch(X,fromIndex,toIndex, x);
	}
	/**
	 * 构建线段树
	 * @param nodeIdx
	 * @param leftIdx
	 * @param rightIdx
	 */
	public void build(int nodeIdx,int leftIdx,int rightIdx){
		if(segTree[nodeIdx]==null)
			segTree[nodeIdx]=new Node();
		
		segTree[nodeIdx].left=leftIdx;
		segTree[nodeIdx].right=rightIdx;
		segTree[nodeIdx].cover=0;// 默认值为0
		segTree[nodeIdx].coverLength=0;
		if(leftIdx+1==rightIdx){// 叶子结点
			return;
		}
		int mid=segTree[nodeIdx].mid();
		build(nodeIdx*2+1, leftIdx, mid);// 构造左子树
		build(nodeIdx*2+2,mid,rightIdx);// 构造右子树
	}
	/**
	 * 更新线段树
	 * @param nodeIdx
	 * @param a 扫描到的线段的左边界
	 * @param b 扫描到的线段的右边界
	 * @param cover
	 */
	public void update(int nodeIdx,int a,int b,int cover){
		Node node=segTree[nodeIdx];
		
		if(node.left==a&&node.right==b){// 重合
			node.cover+=cover;
			getLen(nodeIdx);
			return;
		}
		int mid=node.mid();
		if (b <= mid) {// 更新左子树
			update(nodeIdx << 1 | 1, a, b, cover);
		} else if (a >= mid) {// 更新右子树
			update(nodeIdx << 1 | 2, a, b, cover);
		} else {// 左右子树都要更新
			update(nodeIdx << 1 | 1, a, mid, cover);
			update(nodeIdx << 1 | 2, mid, b, cover);
		}
		getLen(nodeIdx);
	}
	/**
	 * 获取某个区间被覆盖的长度
	 * @param nodeIdx
	 */
	public void getLen(int nodeIdx){
		Node node=segTree[nodeIdx];
		
		if(node.cover>0){// 被覆盖
			node.coverLength= X[node.right]-X[node.left];
		}else if(node.right==node.left+1){// 叶子结点,未被覆盖
			node.coverLength=0;
		}else{// 是线段,但没有完全覆盖,其覆盖长度需要根据孩子结点来计算
			node.coverLength=segTree[nodeIdx*2+1].coverLength+segTree[nodeIdx*2+2].coverLength;
		}
	}
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		Main m=new Main();
		int mCase=1;
		float area=0;// 面积
		try{
			m.N=sc.nextInt();
			
			while(m.N!=0){
				
				if(m.N<0||m.N>100)
					throw new Exception("N不合法");
				
				m.input(sc);// 输入坐标
				m.transfer();// 对输入数据进行处理
				m.sort(m.lines,0,m.N*2);// 线段排序
				m.sort(m.X,0,m.N*2);// 横坐标排序
				int end=1;
				for(int i=1;i<2*m.N;i++){// 横坐标去重
					if(m.X[i]!=m.X[i-1]){
						m.X[end++]=m.X[i];
					}
				}
				end--;
				m.build(0, 0, end);// 构造线段树
				
				area=0;
				for(int i=0;i<2*m.N-1;i++){// 扫描,最后一条线段不用扫描了
					Line line=m.lines[i];
					int a=m.search(m.X, 0, end+1,line.x1);// 在坐标数组中找到线段起点和终点对应的数组下标
					int b=m.search(m.X, 0, end+1, line.x2);
					
					m.update(0, a, b, line.seq);// 更新线段树

					float dy=m.lines[i+1].y-line.y;// 当前线段和下一条线段y坐标的差值
					area+=m.segTree[0].coverLength*dy;// 计算面积
				}
				
				DecimalFormat format=new DecimalFormat("0.00");

				if(mCase==1)
					System.out.println();
				System.out.println("Build #"+mCase++);
				System.out.println("Total explored area: "+format.format(area));// 输出
				
				m.N=sc.nextInt();// 下一组输入
			}
		} catch (Exception e) {
//			e.printStackTrace();
		}finally{
			sc.close();
		}
	}
	/**
	 * 保存线段
	 * @author fangxuan
	 *
	 */
	public class Line{
		public float x1,x2;
		public float y;
		public int seq;// 边序号,用来区别上下边
		public Line(float x1, float x2, float y) {
			this.x1 = x1;
			this.x2 = x2;
			this.y = y;
		}
	}
	/**
	 * 线段树的结点
	 * @author fangxuan
	 *
	 */
	public class Node{
		public int left;// 保存左边界在横坐标数组中的下标
		public int right;// 保存右边界在横坐标数组中的下标
		public int cover;// 0 线段未被覆盖;1 被覆盖
		public float coverLength;// 该区间被覆盖的长度
		public int mid(){// 求中间值
			return (left+right)>>1;
		}
	}
}

用List集合代替数组,会Memory Limit Exceed

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class Main {
	public static class Line {
		public float x1, x2, y;
		public int seq;

		public Line(float x1, float x2, float y, int seq) {
			this.x1 = x1;
			this.x2 = x2;
			this.y = y;
			this.seq = seq;
		}
	}

	public static class Segment {
		public int left, right;
		public int cover;
		public float coverLength;
		public Segment lChild, rChild;

		public int mid() {
			return (left + right) >> 1;
		}
	}

	public static class SegTree {
		public Segment root;
		public List<Float> xList;

		public void build(List<Float> xList) {
			this.xList = xList;
			int size = xList.size();
			if (size == 0) {
				root = null;
			} else
				root = build(0, size - 1);
		}

		private Segment build(int left, int right) {
			Segment root = new Segment();
			root.cover = 0;
			root.coverLength = 0;
			root.left = left;
			root.right = right;
			if (left + 1 == right) {
				root.lChild = root.rChild = null;
			} else {
				int mid = root.mid();
				root.lChild = build(left, mid);
				root.rChild = build(mid, right);
			}
			return root;
		}

		public void update(Line line) {
			if (root == null)
				return;
			int a = search(xList, line.x1);
			int b = search(xList, line.x2);
			update(root, a, b, line.seq);
		}

		private void update(Segment root, int a, int b, int seq) {
			if (root.left == a && root.right == b) {
				root.cover += seq;
				getCoverLength(root);
				return;
			}
			int mid = root.mid();
			if (b <= mid) {
				update(root.lChild, a, b, seq);
			} else if (a >= mid) {
				update(root.rChild, a, b, seq);
			} else {
				update(root.lChild, a, mid, seq);
				update(root.rChild, mid, b, seq);
			}
			getCoverLength(root);
		}

		public void getCoverLength(Segment root) {
			if (root.cover > 0) {
				int r = root.right;
				int l = root.left;
				root.coverLength = xList.get(r) - xList.get(l);
			} else if (root.left + 1 == root.right) {
				root.coverLength = 0;
			} else {
				root.coverLength = root.lChild.coverLength
						+ root.rChild.coverLength;
			}
		}
	}

	public static int search(List<Float> xList, float x) {
		return Collections.binarySearch(xList, x);
	}

	public static void sortLine(List<Line> lines) {
		Collections.sort(lines, new Comparator<Line>() {
			@Override
			public int compare(Line o1, Line o2) {

				return (int) (o1.y - o2.y);
			}
		});
	}

	public static void sortX(List<Float> xList) {
		Collections.sort(xList);
		int size = xList.size();
		for (int i = size - 1; i > 0; i--) {
			if (xList.get(i) == xList.get(i - 1)) {
				xList.remove(i);
			}
		}
	}

	public static void main(String[] args) {
		int n;
		int mCase = 0;
		float area = 0;
		int i = 0;

		List<Float> xList = new ArrayList<Float>();
		List<Line> lines = new ArrayList<Main.Line>();
		Scanner sc = new Scanner(System.in);

		n = sc.nextInt();
		while (n != 0) {

			xList.clear();
			lines.clear();

			float x1;
			float y1;
			float x2;
			float y2;

			for (i = 0; i < n; i++) {
				x1 = sc.nextFloat();
				y1 = sc.nextFloat();
				x2 = sc.nextFloat();
				y2 = sc.nextFloat();

				xList.add(x1);
				xList.add(x2);

				lines.add(new Line(x1, x2, y1, 1));
				lines.add(new Line(x1, x2, y2, -1));
			}

			sortX(xList);
			sortLine(lines);

			SegTree segTree = new SegTree();
			segTree.build(xList);
			if (segTree.root != null) {

				area = 0;
				for (i = 0; i < lines.size() - 1; i++) {

					segTree.update(lines.get(i));

					area += segTree.root.coverLength
							* (lines.get(i + 1).y - lines.get(i).y);
				}

				System.out.println("Build #" + (++mCase));
				System.out.printf("Total explored area: %.2f\n", area);
			}

			n = sc.nextInt();
		}

		sc.close();
	}
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值