【Timus Online Judge】 1019 Line Painting
·成段线段树更新+离散化:
Line Painting
Memory Limit: 16 MB
Input
ai bi ci
where ai and bi are integers, ci is symbol 'b' or 'w', ai, bi, ci are separated by spaces.
This triple of parameters represents repainting of segment from ai to bi into color ci ('w' — white, 'b' — black). You may assume that 0 < ai < bi < 109.
Output
Sample
| input | output |
|---|---|
4 1 999999997 b 40 300 w 300 634 w 43 47 b |
47 634 |
---------------------------------------------------------------------------------------------------------------------------------------------------------
题意:初始有一段坐标值为0~10^9的白色线段,现进行区间涂色更新操作,问所有操作(操作次数不多于5000次)结束后这个线段上白色最长的区间。
思路:很明显用线段树来解决区间连续的问题,而且数据给的也很直白,一目了然是用离散化来解决。
总结下关于线段树的离散化的应用:
离散化是为了解决题目给出的数据范围过大,线段树数组开不下的矛盾。一般看到数据范围很大,而操作数的值一般的情况,肯定是离散化了。这道题目就是一个经典的例子。这里的离散化,我认为类似是一种Hash的思想,建立一个映射的机制,将大数值通过一定关系用小数值表示出来。我们知道一般内存下,int的数组约莫可以开8*10^6,那么要想用线段树来解决问题,一般我们开的node的大小也局限于此。以本题为例,如果不离散化,我们至多只能表示到10^6,显然无法解决这个问题,但是看到操作次数最多仅仅只有5000次,这个数据肯定会引起我们的思考:不同的坐标值至多出现10000次,如果我们将这个整线段不均匀划分成10000段,并且重新为他的坐标标号:1~10000,那么我们就可以用线段树有效解决这类问题了。
我惯用的做法是额外开一个数组来保存出现过的值(本题是坐标),用数组的下标值代替原坐标值出现在线段树节点中。其实就是将我们实际中的不均匀划分(线段长度不一)映射为均匀划分(标号统一)。见下图:

离散化确实蛮好的,但是用起来还是觉得麻烦了些。想到这个题目写了将近3个小时就觉得心寒啊...还需要多多练习呢!
LinePainting
1 /* 2 PRO : Line Painting 3 TIM : 8.13/2012 4 */ 5 #include <iostream> 6 #include <cstdio> 7 #include <cstdlib> 8 #include <cstring> 9 #include <algorithm> 10 #define L(x) x<<1 11 #define R(x) x<<1|1 12 using namespace std; 13 const int MAXN = 5500; 14 int yy[2*MAXN]; 15 16 struct node1 17 { 18 int l, r; 19 char str[3]; 20 }seg[MAXN]; 21 22 struct node 23 { 24 int llen, len, rlen; 25 int l, r, mid, add; 26 bool flag; 27 }tree[100*MAXN]; //线段树的节点,llen存储该节点左起最大区间、len记录该区间最大长度区间、rlen记录该节点右起最大区间 28 // l为该节点左边界,r为右边界,mid为中间点,add为懒惰标记; 29 int Max(int x, int y) 30 { 31 return x>y ? x : y; 32 } 33 34 void BuildTree(int index, int left, int right) 35 { 36 tree[index].l = yy[left]; 37 tree[index].r = yy[right]; 38 tree[index].mid = (left+right)>>1; 39 tree[index].add = 0; 40 tree[index].flag = false; 41 tree[index].llen = tree[index].len = tree[index].rlen = yy[right]-yy[left]; //初始化为白线; 42 if(left+1 == right){ //由于是区间,故其子节点应该是长度为1的线段; 43 tree[index].flag = true; //标记为叶子节点 44 return ; 45 } 46 BuildTree(L(index),left,tree[index].mid); 47 BuildTree(R(index),tree[index].mid, right); //递归过程 48 } 49 50 void Update(int index, int left, int right, int add) 51 { 52 if(tree[index].flag==false && tree[index].add!=0){ //懒惰标记,更新到俩儿子节点 53 if(tree[index].add==-1){ //如果之前标记为-1,说明该区间都涂黑色,则儿子的最大长度都为0; 54 tree[L(index)].llen = tree[L(index)].len = tree[L(index)].rlen = 0; 55 tree[R(index)].llen = tree[R(index)].len = tree[R(index)].rlen = 0; 56 } 57 else if(tree[index].add==1){ //反之,同理 58 tree[L(index)].llen = tree[L(index)].len = tree[L(index)].rlen = tree[L(index)].r-tree[L(index)].l; 59 tree[R(index)].llen = tree[R(index)].len = tree[R(index)].rlen = tree[R(index)].r-tree[R(index)].l; 60 } 61 tree[L(index)].add = tree[index].add; //将标记更新到儿子节点,并将自身标记清零 62 tree[R(index)].add = tree[index].add; 63 tree[index].add = 0; 64 } 65 66 if(tree[index].l==left && tree[index].r == right){ //懒惰标记:更新到完整区间后就停止,不再继续下去,只标记add值 **核心所在** 67 if(add==-1) tree[index].llen = tree[index].len = tree[index].rlen = 0; 68 else tree[index].llen = tree[index].len = tree[index].rlen = right - left; 69 tree[index].add = add; 70 return ; 71 } 72 73 if(right<=yy[tree[index].mid]) Update(L(index),left,right,add); 74 else if(left>=yy[tree[index].mid]) Update(R(index), left, right,add); 75 else{ 76 Update(L(index),left, yy[tree[index].mid], add); 77 Update(R(index),yy[tree[index].mid], right, add); 78 } 79 80 tree[index].len = Max(tree[L(index)].rlen+tree[R(index)].llen, Max(tree[R(index)].len,tree[L(index)].len)); 81 //该节点的最长长度应该是他的左儿子最长长度、右儿子最长长度、以及中间段可拼接最长长度 中的最长长度 82 tree[index].llen = tree[L(index)].llen; 83 if(tree[L(index)].llen == tree[L(index)].r-tree[L(index)].l) tree[index].llen += tree[R(index)].llen; 84 //该节点左起最长长度。果tree[L(index)].llen == tree[L(index)].r-tree[L(index)].l+1 85 //如说明他左起最长区间要比左儿子的llen长(如果右儿子的llen!=0); 86 tree[index].rlen = tree[R(index)].rlen; 87 if(tree[R(index)].rlen == tree[R(index)].r-tree[R(index)].l) tree[index].rlen += tree[L(index)].rlen; 88 //该节点右起最长长度。同理 89 } 90 91 int Query(int index, int left, int right) 92 { 93 if(tree[index].flag==false && tree[index].add!=0){ //Query中也需要注意更新的问题 94 if(tree[index].add==-1){ 95 tree[L(index)].llen = tree[L(index)].len = tree[L(index)].rlen = 0; 96 tree[R(index)].llen = tree[R(index)].len = tree[R(index)].rlen = 0; 97 } 98 else { 99 tree[L(index)].llen = tree[L(index)].len = tree[L(index)].rlen = tree[L(index)].r-tree[L(index)].l; 100 tree[R(index)].llen = tree[R(index)].len = tree[R(index)].rlen = tree[R(index)].r-tree[R(index)].l; 101 } 102 tree[L(index)].add=tree[index].add; 103 tree[R(index)].add = tree[index].add; 104 tree[index].add = 0; 105 } 106 107 if(tree[index].len==tree[index].r-tree[index].l) return tree[index].l; 108 //下面三条语句的顺序很重要,因为题目要求的是坐标值最小的区间,故应该从左至右寻找 109 if(tree[L(index)].len==tree[index].len) return Query(L(index),left, right); 110 else if(tree[L(index)].rlen+tree[R(index)].llen == tree[index].len) return tree[L(index)].r-tree[L(index)].rlen; 111 else return Query(R(index),left, right); 112 } 113 114 int main() 115 { 116 int i, j, n, a, b, cur=1, st, t; 117 while(~scanf("%d",&n)){ 118 yy[1] = 0; 119 for(i=1; i<=n; ++i){ 120 scanf("%d %d %s",&a,&b,seg[i].str); 121 if(a>b){ 122 t = a; 123 a = b; 124 b = t; 125 } 126 yy[++cur] = a; //额外用一个数组来存储坐标值,对应的数组的下标志为离散化后 线段树中的值 127 yy[++cur] = b; 128 seg[i].l = a; //保存操作更新的区间 129 seg[i].r = b; 130 } 131 yy[++cur] = 1000000000; 132 sort(yy+1,yy+cur+1); 133 134 BuildTree(1,1,cur); 135 for(i=1; i<=n; ++i){ 136 if(seg[i].str[0]=='b') Update(1,seg[i].l,seg[i].r,-1); 137 else Update(1,seg[i].l,seg[i].r,1); 138 } 139 140 st = Query(1,1,cur); 141 printf("%d %d\n",st, st+tree[1].len); 142 } 143 return 0; 144 }

浙公网安备 33010602011771号