// 20884K 329MS G++ #include <stdio.h> #include <string.h> #include <stdlib.h> int posterNum; int ep[20010]; unsigned short dis[10000010]; int epNum; int cmp(const void * p1, const void * p2) { return (*((int*)p1)) - (*((int*)p2)); } struct PosterRange { int begin; int end; }; typedef struct PosterRange PosterRange; PosterRange posterInfo[10005]; int endMax; struct TreeNode { int posterId; // -1, no poster in this range yet, -3, one poster here but not cover whole range. -2, some posters here, but not cover this range. >=0 , only 1 poster occupy here }; typedef TreeNode TreeNode; TreeNode treeHead[200050]; void updateTree(int curNodeId, int posterId, int posterBegin, int posterEnd, int rangeBegin, int rangeEnd) { int mid = (rangeBegin + rangeEnd)>>1; if (posterBegin > posterEnd) { return; } if ((posterBegin > rangeEnd) || (posterEnd < rangeBegin)) { return; } int curPosterId = treeHead[curNodeId].posterId; if ((posterBegin <= rangeBegin) && (posterEnd >= rangeEnd)) {// whole rage overred by poster treeHead[curNodeId].posterId = posterId; } else if ((posterBegin == posterEnd) && (rangeBegin == rangeEnd)) { // only one block treeHead[curNodeId].posterId = posterId; } else if (curPosterId == -1) { // first time occupy, but not whole cover this range treeHead[curNodeId].posterId = -3; if (posterEnd <= mid) { // whole range in left part updateTree(curNodeId<<1, posterId, posterBegin, posterEnd, rangeBegin, mid); } else if ((posterBegin <= mid) && (posterEnd >= mid+1)) { // range in both parts updateTree(curNodeId<<1, posterId, posterBegin, mid, rangeBegin, mid); updateTree(curNodeId<<1|1, posterId, mid+1, posterEnd, mid+1, rangeEnd); } else if (posterBegin >= mid + 1) { // whole range in right part updateTree(curNodeId<<1|1, posterId, posterBegin, posterEnd, mid+1, rangeEnd); } } else { // if has been occupied before, and current poster can not cover whole range int prevPosterId = treeHead[curNodeId].posterId; treeHead[curNodeId].posterId = -2; if (prevPosterId >=0) { // if this range is whole covered by prev color treeHead[curNodeId<<1].posterId = prevPosterId; treeHead[curNodeId<<1|1].posterId = prevPosterId; } if (posterEnd <= mid) { // whole range in left part updateTree(curNodeId<<1, posterId, posterBegin, posterEnd, rangeBegin, mid); } else if ((posterBegin <= mid) && (posterEnd >= mid+1)) { // range in both parts updateTree(curNodeId<<1, posterId, posterBegin, mid, rangeBegin, mid); updateTree(curNodeId<<1|1, posterId, mid+1, posterEnd, mid+1, rangeEnd); } else if (posterBegin >= mid+1) { // whole range in right part updateTree(curNodeId<<1|1, posterId, posterBegin, posterEnd, mid+1, rangeEnd); } } // printf("%d %d %d %d %d %d\n", curNodeId, posterBegin, posterEnd, rangeBegin, rangeEnd, treeHead[curNodeId].posterId); } int getPosterShowNum(int curNodeId) { int curPosterId = treeHead[curNodeId].posterId; // printf("get curPosterId:%d %d\n", curNodeId, curPosterId); if (curPosterId == -1) { // no poster occupy yet return 0; } else if (curPosterId >= 0) { // only 1 poster occupy. if (!ep[curPosterId]) { ep[curPosterId] = 1; return 1; } else { return 0; } } else if (curPosterId == -2 || curPosterId == -3) { // more than 1 poster occupy this range int leftNum = getPosterShowNum(curNodeId<<1); int rightNum = getPosterShowNum(curNodeId<<1|1); return leftNum + rightNum; } return 0; } int main() { int caseNum; scanf("%d", &caseNum); for (int i = 0; i < caseNum; i++) { scanf("%d", &posterNum); int posterBegin , posterEnd; memset(ep, 0, sizeof(ep)); memset(dis, 0, sizeof(dis)); memset(posterInfo, 0, sizeof(posterInfo)); epNum = 0; endMax = 0; for (int i = 0; i < posterNum; i++) { scanf("%d %d", &posterBegin, &posterEnd); posterInfo[i].begin = posterBegin; posterInfo[i].end = posterEnd; if (!dis[posterBegin]) { ep[epNum++] = posterBegin; dis[posterBegin] = 1; } if (!dis[posterEnd]) { ep[epNum++] = posterEnd; dis[posterEnd] = 1; } } qsort(ep, epNum, sizeof(int), cmp); endMax = epNum; for (int i = 0; i < 200050; i++) { treeHead[i].posterId = -1; } memset(dis, 0, sizeof(dis)); for (int i = 0; i < epNum; i++) { dis[ep[i]] = i + 1; } for (int i = 0; i < posterNum; i++) { int dPosterBegin = dis[posterInfo[i].begin]; int dPosterEnd = dis[posterInfo[i].end]; // printf("A\n"); updateTree(1, i, dPosterBegin, dPosterEnd, 1, epNum); } memset(ep, 0, sizeof(ep)); int showPosterNum = getPosterShowNum(1); printf("%d\n", showPosterNum); } }
Alberta Collegiate Programming Contest 2003.10.18
http://ugweb.cs.ualberta.ca/~acpc/2003/
Problem G
线段树的精髓是,能不往下搜索,就不要往下搜索,尽可能利用子树的根的信息去获取整棵子树的信息。如果在插入线段或检索特征值时,每次都非要搜索到叶子,还不如直接建一棵普通树更来得方便。一个严谨的题解
http://blog.csdn.net/niuox/article/details/9073367
我下边最初的解题思路其实算是不对的,不严谨,没有染过色的区间,如果第一次染色,就把整个区间给染上这个色,是有问题的:
比如
区间 [1 4], 如果最开始来一个 [4 4 1(颜色)] ,那么 区间 [1 4]直接变为1, 如果再来一个 [3 4 2], 那么 因为pushdown,
[1 2] 和 [3 4]会先变成 1, 然后 [3 4 2] 覆盖 [3 4]为2, [1 4]为 -2(混色),最后就变为:
[1 4] = -2, [1 2] = 1 [3 4] =2, 这样最后统计会有两种颜色,是错误的,原因就是pushdown 把本来不属于 1 的 [1 2]给染成 1了,所以错误,
因此下面的思路其实有问题,还是应该按照上面的题解严谨操作,每次只有保证完全覆盖某个区间,才将区间染色,否则递归子区间(知道找到完全覆盖子区间为止),这样做,保证了pushdown不会出现上面的问题(因为是覆盖整个区间,因此必然子区间也被覆盖)就是最后求颜色数量时,
可能会走的比较深,但是是正确的.
唉, 虽然是线段树水题,但是对于不怎么熟悉的人来说,真费劲,并且虽然这个程序AC了,但是其实并不正确,此题AC的很多人应该都是通不过这组数据的:
2 3 5 6 4 5 6 8 3 1 10 1 3 6 10结果应该是 2, 3 但是很多人是2,2, 是因为离散化没有处理妥当.
第二个case中的原始数据: 1 3 6 10 离散化以后对应为 1 2 3 4,
那么输入数据也就变为:
1 4
1 2
3 4
从这组数据看,1 4完全被 1 2 和 3 4完全覆盖了(这里也要对题意理解正确,题目给的每个数字对应的是wall的某一个block, 比如 5 6 对应的就是第5 和 第6块block,
其实是两个block,而不是从5到6的这一个block, 因此本题的线段树是最后可能要化成点的,及 5,5 6,6 分别都对应两个单独block),但其实 2 和 3中间是有空缺的,1 到 4的poster在这段空缺中是可以看到的,所以在离散化是要考虑这种特殊情况,mark一下。
这道题算是线段树的基础应用,本质是经典的着色问题,那么对于线段树的每段区间,要记录一个当前的颜色值(在本题,就是不同的posterId)
-1表示区间R还从来没有被着过色,
>=0表示此区间目前只有这一种颜色,并且是完全覆盖此区间的。
-3表示此区间之存在一种颜色,但是没有完全覆盖此区间.
-2表示此区间当前存在不止一种的颜色,。
本题同时还要求进行离散化,不但能节省空间,还可以节省时间(因为等于把整个区间的范围也给压缩了,比如从 1 10000 压到 1 100, 那么整个线段树的树高就变小了,自然时间也提上去了),离散化就是先收集所有出现的数字(注意搞一个bitmap来排除重复出现的数字),然后进行排序,最后根据其次序进行一一映射,
比如 1 3 6 10
就对应了 1 2 3 4,
然后就是不断的插入颜色的范围和颜色值来构建最终的线段树了(线段树其实很灵活,每个treenode记录不同的信息就可以解决不同的问题,像一个良好的模版方案)
还是直接开了一个大数组来模拟树,之前试着每次malloc treenode,果断的TLE了, 经过离散化以后,这个大数组的尺寸就在题目接受范围内了(最多20000个不同数,那么么离散范围就是 1~ 20000, 大数字只需要 40000)。
插入颜色(b(起始), e(终点), i(颜色值)) 到 区间 (rB, rE)时,会有这些case:
首先要判断区间,如果(rB, rE)被 (b,e) 完全覆盖,那么将直接将此区间染色返回即可.
然后:
[1] 如果区间的颜色值 C == -1, 那么说明此区间还没有被染色,直接修改此区间颜色值为 -3(此区间有一种颜色,但是没有完全覆盖), 同时,继续向下走:
<1> 如果 (b,e) 在 (rB,rE)的左半部分,那么递归的将 (b, e, i) 插入到 (rB, (rB+rE)/2)
<2> 如果 (b,e) 在 (rB, rE)的右半部分,那么递归的将 (b,e,i) 插入到((rB+rE)/2+1, rE)中(注意是,(rB+rE)/2+1, 因为要考虑每个点)
<3>如果(b,e) 横跨了(rB, rE), 那么两次插入: (b, (rB+rE)/2, i) -> (rB, (rB+rE)/2) ((rB+rE)/2+1, e, i) ->((rB+rE)/2+1, rE),
一直这样,直到最后找到一个区间, (b, e) 可以完全覆盖(rB,rE)。
[2] 如果区间的颜色C>=0. 那么说明此区间之前已经有完全覆盖的染色了,那么此区间颜色变为-2,表示此区间有多余一种的颜色,让此区间的左右child区间继承此区间的颜色, 然后和上面的分析一样,进行向下插入。
[3]如果区间的颜色C == -2/-3, 那直接向下进行插入可以了。
最后统计颜色值时,
从线段树的root开始递归的求getNum(),
如果其C==-1 直接return 0,
如果 C>=0,return 1
如果 C==-2/-3, 那么 return getNun(leftChild) + getNum(rightChild)