poj-1436

13 篇文章 0 订阅
// 2240K	1297MS	C++
#include <cstdio>
#include <vector>
#include <cstring>
#include <cstdlib>
#include <set>

using namespace std;

const int MAX = 8010 * 2;

set<int> lineVisibleInfo[8000];

struct TreeNode {
	int left;
	int right;
	int lineId;
};

typedef struct TreeNode TreeNode;

struct LineInfo {
	int y1;
	int y2;
	int x;
};

typedef struct LineInfo LineInfo;

TreeNode tree[MAX<<2];
LineInfo lines[8003];

void buildTree(int pos, int begin, int end) {
	// printf("buildTree %d %d\n", begin, end);
	TreeNode & curNode = tree[pos];
	curNode.left = begin;
	curNode.right = end;
	curNode.lineId = -1;
	if (begin == end) {
		return;
	} else {
		int mid = (begin + end)>>1;
		buildTree(pos<<1, begin, mid);
		buildTree(pos<<1|1, mid+1, end);
	}
}

void pushDown(int pos) {
	TreeNode & curNode = tree[pos];
	// printf("pushDown %d %d %d\n", curNode.left, curNode.right, curNode.lineId);
	TreeNode & leftNode = tree[pos<<1];
	TreeNode & rightNode = tree[pos<<1|1];
	if (curNode.lineId > -1) {
		leftNode.lineId = curNode.lineId;
		rightNode.lineId = curNode.lineId;
		// printf("pushDownLeft %d %d %d\n", leftNode.left, leftNode.right, leftNode.lineId);
		// printf("pushDownRight %d %d %d\n", rightNode.left, rightNode.right, rightNode.lineId);
		curNode.lineId = -1;
	}
}

int lineNum;
int caseNum;

int comp(const void * p1, const void * p2) {
	return (*((LineInfo*)p1)).x - (*((LineInfo*)p2)).x;
}

#define INF 999999

void query(int pos, int rangeLeft, int rangeRight, int newLineId) {
	TreeNode & curNode = tree[pos];
	int left = curNode.left;
	int right = curNode.right;
	int lineId = curNode.lineId;
	// printf("query %d %d %d %d %d\n", rangeLeft, rangeRight, left, right, lineId);

	if (lineId > -1) {
		// if (left <= rangeLeft && right >= rangeRight) {
			// printf("line %d insert in %d\n", lineId, newLineId);
			lineVisibleInfo[newLineId].insert(lineId);
			// lineVisibleInfo[lineId].insert(newLineId);
			return;
		// }
	}

	if (left < right) {
		pushDown(pos);
		int mid = (left + right)>>1;
		if (rangeRight <= mid) {
			query(pos<<1, rangeLeft, rangeRight, newLineId);
		} else if (rangeLeft <= mid && rangeRight > mid) {
			query(pos<<1, rangeLeft, mid, newLineId);
			query(pos<<1|1, mid + 1, rangeRight, newLineId);
		} else if (rangeLeft > mid) {
			query(pos<<1|1, rangeLeft, rangeRight, newLineId);
		}
	}
}

void update(int pos, int rangeLeft, int rangeRight, int newLineId) {
	// printf("update %d %d\n", rangeLeft, rangeRight);
	TreeNode & curNode = tree[pos];
	int left = curNode.left;
	int right = curNode.right;
	int lineId = curNode.lineId;

	if (rangeLeft <= left && rangeRight >= right) {
		curNode.lineId = newLineId;
		// printf("update %d %d -> %d\n", left, right, curNode.lineId);
		return;
	}
	pushDown(pos);
	int mid = (left + right)>>1;
	if (rangeRight <= mid) {
		update(pos<<1, rangeLeft, rangeRight, newLineId);
	} else if (rangeLeft <= mid && rangeRight > mid) {
		update(pos<<1, rangeLeft, mid, newLineId);
		update(pos<<1|1, mid + 1, rangeRight, newLineId);
	} else if (rangeLeft > mid) {
		update(pos<<1|1, rangeLeft, rangeRight, newLineId);
	}
}

int main() {
	scanf("%d", &caseNum);
	for (int i = 0; i < caseNum; i++) {
		scanf("%d", &lineNum);
		for (int i = 0; i < lineNum; i++) {
			lineVisibleInfo[i].clear();
		}
		int minY1 = INF;
		int maxY2 = -INF;
		for (int j = 0; j < lineNum; j++) {
			int y1, y2, x;
			scanf("%d %d %d", &y1, &y2, &x);
			y1 *= 2;
			y2 *= 2;
			minY1 = minY1 < y1 ? minY1: y1;
			maxY2 = maxY2 > y2 ? maxY2: y2;
			lines[j].y1 = y1;
			lines[j].y2 = y2;
			lines[j].x = x;
		}
		qsort(lines, lineNum, sizeof(LineInfo), comp);
		// for (int k = 0; k < lineNum; k++) {
		// 	printf("%d %d %d\n", lines[k].y1, lines[k].y2, lines[k].x);
		// }
		buildTree(1, minY1, maxY2);

		for (int k = 0; k < lineNum; k++) {
			query(1, lines[k].y1, lines[k].y2, k);
			update(1, lines[k].y1, lines[k].y2, k);
		}
		int res = 0;
		for (int k = lineNum-1; k >= 0; k--) {
			// printf("Line %d:", k);
			set<int>::iterator it = lineVisibleInfo[k].begin();
			for (;it != lineVisibleInfo[k].end(); it++) {
				// printf(" %d", *it);
				set<int>::iterator it2 = lineVisibleInfo[k].begin();
				for (;it2 != lineVisibleInfo[k].end(); it2++) {
					if (*it2 <= *it) {
						continue;
					}
					// printf("%d %d\n", *it2, *it);
					if (lineVisibleInfo[*it2].find(*it) != lineVisibleInfo[*it2].end()) {
						res++;
					}
				}
			}
			// printf("\n");
		}
		printf("%d\n", res);
	}
}


一开始没有读懂题意,还以为水平可见是指必须有一条水平直线能连接两条垂直线的的端点才行,后来才发现其实并不这个意思,只要一条水平线,能相交于这个两个垂直线段并且不与其他的垂直线段相交即可, 这样就可以用线段树的染色解法来做了, 将每条线段以 x 从小到大或是从大到小的顺序依次加入到线段树中,在将某个垂直线加入线段树之前,先利用线段树之前的信息来查看此垂直线的范围内共有几种不同的染色(每种染色对应这之前加入到的线段),就可以得到此垂直线段与之前的垂直线段中哪些是可以用一条水平线同时相交且不经过其他垂直线的,在全部的垂直线的这种信息拿到以后,就可以从最后一根垂直线开始检查,看是与其水平可见的两条垂直线是否也是水平可见。

这道题有个很重要的处理细节:

因为最终要化为线段点(即线段树的最下层节点代表的其实一个点[k, k]), 那么么 如果有这样4条垂直线:

(0, 4, 1)

(0, 1, 2)

(2, 4, 2)

(0, 4, 3)

其中(0, 1, 2)与 (0,2,2) 在线段树中会将(0, 4)整个区间占据,但是其实 在 1 到 2之前还是有空挡的(线段树因为是线段点 所以体现不出这一点来), 使得有水平线可以相交于 (0, 4, 1)和 (0,4,3), 因此为了消除这个误差,就要对数据进行预处理,将所有线段的端点值都乘以2,

这样就变成:

(0,8, 1)

(0, 2, 2)

(4 ,8, 2)

(0, 8, 3)

这样 [0, 2] 和 [4, 8]就可以再线段树层面体现出中间的空挡了。

在数据输入的时候,也最好将这些线段树中的最小y和最大y得到,这样最后建树的时候就不会有冗余,提高线段树的操作效率(否则就要按照从 0 到 题目给的y的最大值的两倍的线段树来做)。

线段树本身和之前处理染色问题的时候一样, 额外保存一个lazytag  C(-1该线段有多余一种颜色)表示此线段的颜色(本题中一种颜色代表一个垂直线段),

在所有垂直线段输入之后,要先按照 x 来对线段进行大小排序(可以按照此顺序从0开始标记每条垂直线段), 然后按照该顺序依次查询和插入, 同时还要一个数组来保存某一条线段的水平可见线段都有哪些(用set实现了,因为set会排重)

在处理第k条线段时:

先查询,在查询时,进入某个区间,如果该区间的C>=-1,说明该区间只有一种颜色, 将此颜色记录在k对应的水平可见线段列表中,

如果C==-1,那么就pushDown(将该层颜色继承给子区间,同时自己的C=-1),然后递归处理其子区间, 因为可能会得到重复的颜色(比如 [1, 2] 和 [4,5]都是颜色1),这时候用set保存水平可见线段就比较方便,自动排重了(因为水平可见是双向的,如果从k到m是水平可见的,那么m到k也应该是水平可见的,所以理论上,此时除了将m加入到k的水平可见列表中外,还要在m的水平可见列表中加入k,不过从本题来看,不用这样做,这样做反而会麻烦, 维持这种单向可见 足够后面使用)。

查询完了以后就讲此线段插入到线段树中去,和之前的染色一模一样,没啥说的。

最后在所有的垂直线段都插入完了以后,就可以求三角(三条垂直线段,两两水平相交)的数量了,

从最后一条处理线段L递减开始:

遍历 L的水平可见列表中任何两个线段K 和 M的组合(注意,是组合,没有前后顺序之分,(K, M)和(M,K)是一样的)检查此两条线段K和M是否是水平可见的(检查K与M中id比较大的那个线段的水平可见列表是否有另外一个即可,因为id是按照x的大小关系来分配的), 如果可见,那么三角数量+1.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值