MemoryGarden's Blog

努力 -----------大能猫

C++博客 首页 新随笔 联系 聚合 管理
  118 Posts :: 11 Stories :: 20 Comments :: 0 Trackbacks



以下记录,有个人见解,如若有错误,欢迎提出,我一定改正。

附件 :  /Files/MemoryGarden/xuemao.rar 

经过了一段时间,终于学会了线段树的基本应用了

线段树的应用非常灵活,我只是学会了皮毛,个人倾向于静态,所以需要以下全局变量。

int tree[max], leftchild[max], rightchild[max], leftvalue[max], rightvalue[max], cover[max],node, coverlength;

线段树的最基本的操作

[1]  建树 :

void construct(int left, int right){
    
int index, mid;
    node
++;
    index 
= node;
    leftvalue[index] 
= left;
    rightvalue[index] 
= right;
    cover[index] 
= 0;
    
if(left + 1 < right){
        mid 
= (left + right) / 2;
        leftchild[index] 
= node + 1;
        construct(left, mid);
        rightchild[index] 
= node + 1;
        construct(mid, right);
    }

}

上述代码利用二分的方法,建立一棵叶子节点为 (i, i+ 1)的线段树 。 特点是每棵树的根,一定包含于子树的线段。

[2] 插入一段线段:

void insert(int index, int c, int d){
    
int mid;
    
if(c <= leftvalue[index] && rightvalue[index] <= d)cover[index]++;
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            mid 
= (leftvalue[index] + rightvalue[index]) / 2;
            
if(c < mid)insert(leftchild[index], c, d);
            
if(d > mid)insert(rightchild[index], c, d);
        }

    }

}

解释 :当要插入的线段[c, d] 覆盖了线段树上的某段线段的时候,将其标记覆盖的cover[]数组自加。如果不覆盖该线段,分为三种情况,可自己画           一下,再根据当前线段于要插入线段的关系,分情况递归左右子树


上图1 为覆盖的情况, 运行
if(c <= leftvalue[index] && rightvalue[index] <= d)cover[index]++;

上图2 为 c < mid  && d < mid   运行
if(c < mid)insert(leftchild[index], c, d);

上图3 为 c > mid && d > mid  运行
if(d > mid)insert(rightchild[index], c, d);
上图4 为 c < mid && d > mid 运行

if(c < mid)insert(leftchild[index], c, d);
if(d > mid)insert(rightchild[index], c, d);



[3]删除一段线段:

void Delete(int index, int c, int d){
    
int mid;
    
if(c <= leftvalue[index] && rightvalue[index] <= d && cover[index])
        cover[index]
--;
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            mid 
= (leftvalue[index] + rightvalue[index]) / 2;
            
if(c < mid)Delete(leftchild[index], c, d);
            
if(d > mid)Delete(rightchild[index], c, d);
        }

    }

}


与插入一样理解。

[4] 统计:

关于统计,有很多,也很灵活,对于每个题目来说,线段树内每个节点内的要表示的有很多,很灵活,这里只说说测度,也就是最后被覆盖的线段的总长度

void count(int index){
    
if(cover[index] > 0)total += y[rightvalue[index]] - y[leftvalue[index]];
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            count(leftchild[index]);
            count(rightchild[index]);
        }

    }

}

理解 :既然某段线段的cover[] 是正数,那么,它的子树也一定被cover,因为前面说过的“特点是每棵树的根,一定包含于子树的线段”
所以长度 = 右边界 - 左边界;

以上为线段树的最基本的操作,是对长度的操作。

对于线段树的应用,还有一种比较广泛,就是类似于涂色的问题

很多大大们都介绍过了, 薛茅的论文上讲解的也非常明白 

这类问题,大多会有一个bj[];数组;

这个数组的用处,是当要插入的线段覆盖了线段树的某一节点段时候,拿染色举例,那么以当前节点位根的子树,都会有相同的颜色,所以我们对它的左右儿子做上标记,在每次对线段树上的节点操作的时候,先检查节点的bj[]数组是否被标记过,如若标记过,那么以当前节点为根的子树的颜色都应该是一样的,也就是bj的颜色,然后把当前节点的bj清楚,当前节点的两个儿子设上标记。 说的笨了些,可以参考我上传得pdf && ppt;

对于染色类型的线段树的操作 :
需要以下的全局变量 只增加了一个bj[]

int tree[max], leftchild[max], rightchild[max], leftvalue[max], rightvalue[max], color[max], bj[max];
int node = 0;

[1] 树的建立 :
void construct(int left, int right){
    
int mid, index;
    node
++;index = node;
    leftvalue[index] 
= left;
    rightvalue[index] 
= right;
    color[index] 
= -2;
    bj[index] 
= -1//代表未设过标记
    if(left + 1 < right){
        mid 
= (left + right) / 2;
        leftchild[index] 
= node + 1;
        construct(left, mid);
        rightchild[index] 
= node + 1;
        construct(mid, right);
    }

}

[2] 处理 bj 节点的clear函数

void clear(int index){
    color[index] 
= bj[index];
    bj[leftchild[index]] 
= bj[index];
    bj[rightchild[index]] 
= bj[index];
    bj[index] 
= -1;
}

仅以颜色举例

[3]树的插入 :
void insert(int index, int c, int d, int x){
    
int mid;
    
if(bj[index] != -1)clear(index);
    
if(color[index] == x)return;
    
if(c <= leftvalue[index] && rightvalue[index] <= d)
        bj[index] 
= x;
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            color[index] 
= -1;
            mid 
= (leftvalue[index] + rightvalue[index]) / 2;
            
if(c < mid)insert(leftchild[index], c, d, x);
            
if(d > mid)insert(rightchild[index], c, d, x);
        }

    }

}
解释 :如果此节点被标记,则当前节点的bj删除,然后两个子节点设上bj

[4] 删除类似

举例 :Count the Colors

按照上述所说,操作即可 代码如下 :

#include <stdio.h>
#include 
<string.h>
#define max 16000
int tree[max], leftchild[max], rightchild[max], leftvalue[max], rightvalue[max], color[max], bj[max];
int node = 0, temp, res[max];
void clear(int index){
    color[index] 
= bj[index];
    bj[leftchild[index]] 
= bj[index];
    bj[rightchild[index]] 
= bj[index];
    bj[index] 
= -1;
}

void construct(int left, int right){
    
int mid, index;
    node
++;index = node;
    leftvalue[index] 
= left;
    rightvalue[index] 
= right;
    color[index] 
= -2;
    bj[index] 
= -1//代表未设过标记
    if(left + 1 < right){
        mid 
= (left + right) / 2;
        leftchild[index] 
= node + 1;
        construct(left, mid);
        rightchild[index] 
= node + 1;
        construct(mid, right);
    }

}


void insert(int index, int c, int d, int x){
    
int mid;
    
if(bj[index] != -1)clear(index);
    
if(color[index] == x)return;
    
if(c <= leftvalue[index] && rightvalue[index] <= d)
        bj[index] 
= x;
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            color[index] 
= -1;
            mid 
= (leftvalue[index] + rightvalue[index]) / 2;
            
if(c < mid)insert(leftchild[index], c, d, x);
            
if(d > mid)insert(rightchild[index], c, d, x);
        }

    }

}


void search(int index){
    
//printf("search %d \n", index);
    if(bj[index] != -1)clear(index);
    
if(color[index] != -1){
        
if(color[index] != temp){
            
if(color[index] >= 0)
                res[color[index]]
++;
            temp 
= color[index];
        }

    }
else{
        search(leftchild[index]);
        search(rightchild[index]);
    }

}


int main(){
    freopen(
"(a,a+1)segement-color.in""r", stdin);
    freopen(
"(a,a+1)segement-color.out""w", stdout);
    
int i, n, c, d, x;
    
while(scanf("%d"&n) != -1){
        node 
= 0;
        memset(res, 
0sizeof(res));
        memset(leftchild, 
0sizeof(leftchild));
        memset(rightchild, 
0sizeof(leftchild));
        construct(
06);
        
for(i = 0; i < n; i++){
            scanf(
"%d%d%d"&c, &d, &x);
            insert(
1, c, d, x);
        }

        search(
1);
        
for(i = 0; i < 8001; i++){
            
if(res[i])
                printf(
"%d %d\n", i, res[i]);
        }

        puts(
"");
    }

    
return 0;
}




关于计算面积的线段树的应用 + 离散化

对于离散化,我的理解也不是很清楚 ,不知道理解的对不对。

如果题目的范围很大,而有效的数据又很少,我们可以将有效数据排序,重新建立坐标轴,然后以有效数据的个数作为范围;

比如有整数类型点 4 5 6 7 则 离散后的数组为 f[0] = 4   f[1] = 5  f[2] = 6 f[3] = 7;
或者浮点类型 4.5    5.6    6.54   7.59 离散后的数组为 f[0] = 4.5  f[1] = 5.6 f[2] = 6.54  f[3] = 7.59;

这样我们就把浮点类型,或者整数类型的有效数据转映射到正数下标的数组里面去了。

我们可以建立一棵(0, 4)的线段树 当插入一条线段 [0, 1] 代表我们插入了[4---5]  或者 [4.5---5.6] 这样一条线段,由于我们将有效数据排序,所以f数组的下表与有效数据的同为递增的关系,所以当我们插入[0, 3]这条线段的时候,也代表了我们插入了[4---7] 或者[4.5---7.59]这样一条线段,[0, 3] 覆盖了[0, 1]这条线段,同样,[4---7] 也会覆盖 [4---5]  或者[4.5---7.59] 也会覆盖[4.5---5.6] 。

以上就是我对离散化的理解了。

举例一题 :Atlantis 

这个题目我们就把横纵坐标都进行离散,然后对于纵坐标,用线段树操作。

把竖直的线段离散后,存储在一个结构体中,存储x坐标, 和上下两端的y坐标,kind用于标记对于一个矩形来说,左边的竖线,还是右边竖线

struct L{
    
double x, y1, y2;
    
bool kind;
}
line[max];

把纵坐标离散,然后排序。按照我上面离散化的意思,建树。

我用的是<map> 能标记重复,也能排序

map<doubleint> Y;
map
<doubleint>::iterator it;


对line 数组按照x坐标排序,对于line数组,逐2个计算横坐标的差,总坐标若为左竖线,则插入,若为右竖线,则删除。。。。说得太不明白了,连我自己都不明白。还是看看代码吧  请大家自己理解。

代码写的不好,在poj上不是0ms。

#include <stdio.h>
#include 
<string.h>
#include 
<algorithm>
#include 
<map>
#include 
<math.h>
#define max 401
#define eps 1e-8
using namespace std;
int tree[max], leftchild[max], rightchild[max], rightvalue[max], leftvalue[max], cover[max];
int node, linelength;
double y[max * 2], total;
struct L{
    
double x, y1, y2;
    
bool kind;
}
line[max];

map
<doubleint> Y;
map
<doubleint>::iterator it;

bool cmp(L a, L b){
    
return a.x < b.x;
}


void construct(int left, int right){
    
int index, mid;
    node
++;
    index 
= node;
    leftvalue[index] 
= left;
    rightvalue[index] 
= right;
    cover[index] 
= 0;
    
if(left + 1 < right){
        mid 
= (left + right) / 2;
        leftchild[index] 
= node + 1;
        construct(left, mid);
        rightchild[index] 
= node + 1;
        construct(mid, right);
    }

}


void insert(int index, int c, int d){
    
int mid;
    
if(c <= leftvalue[index] && rightvalue[index] <= d)cover[index]++;
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            mid 
= (leftvalue[index] + rightvalue[index]) / 2;
            
if(c < mid)insert(leftchild[index], c, d);
            
if(d > mid)insert(rightchild[index], c, d);
        }

    }

}


void Delete(int index, int c, int d){
    
int mid;
    
if(c <= leftvalue[index] && rightvalue[index] <= d && cover[index])
        cover[index]
--;
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            mid 
= (leftvalue[index] + rightvalue[index]) / 2;
            
if(c < mid)Delete(leftchild[index], c, d);
            
if(d > mid)Delete(rightchild[index], c, d);
        }

    }

}




void count(int index){
    
if(cover[index] > 0)total += y[rightvalue[index]] - y[leftvalue[index]];
    
else{
        
if(leftvalue[index] + 1 < rightvalue[index]){
            count(leftchild[index]);
            count(rightchild[index]);
        }

    }

}


int main(){
    freopen(
"1043.in""r", stdin);
    freopen(
"1043.out""w", stdout);
    
int n, i, j, num, c, d, o = 1;
    
double lx, ly, rx, ry, area;
    
while(scanf("%d"&n), n){
        linelength 
= node = num = 0;
        Y.clear();area 
= total = 0.0
        
for(i = 0; i < n; i++){
            scanf(
"%lf%lf%lf%lf"&lx, &ly, &rx, &ry);
            line[linelength].x 
= lx;
            line[linelength].y1 
= ly;
            line[linelength].kind 
= true;
            line[linelength
++].y2 = ry;
            line[linelength].x 
= rx;
            line[linelength].y1 
= ly;
            line[linelength].kind 
= false;
            line[linelength
++].y2 = ry;
            it 
= Y.find(ly);
            
if(it == Y.end())Y.insert(make_pair(ly, num++));
            it 
= Y.find(ry);
            
if(it == Y.end())Y.insert(make_pair(ry, num++));
        }

        
for(it = Y.begin(), i = 0; it != Y.end(); it++, i++){
            y[i] 
= it->first;
            it
->second = i;
        }

        sort(line, line 
+ linelength, cmp);
        construct(
0, num);
        
for(i = 0; i < linelength - 1; i++){
            c 
= Y.find(line[i].y1)->second;
            d 
= Y.find(line[i].y2)->second;
            
if(line[i].kind)insert(1, c, d);
            
else Delete(1, c, d);
            total 
= 0.0;
            count(
1);
            area 
+= total * (line[i + 1].x - line[i].x);
        }

        printf(
"Test case #%d\nTotal explored area: %.2lf\n\n", o++, area);
    }

    
return 0;
}



ps :切记 如果要建立一棵[0, a] 的一棵线段树,我们需要2 * a 个节点。 我因为这个wa了n次。



这是我所有学过的线段树的知识,还会继续学习,如果有毛病,请大家指出,菜鸟在这里谢过;


posted on 2009-04-11 02:55 memorygarden 阅读(1889) 评论(2)  编辑 收藏 引用 所属分类: 报告

Feedback

# re: 线段树 总结 2010-04-26 12:21 慢慢来
染色的,search()中的temp干嘛的?  回复  更多评论
  

# re: 线段树 总结 2010-04-26 15:56 慢慢来
问题还是有的,例如temp应该赋一个值,例如-99,像你int temp全局变量,系统初始值为0,然而题目输入里有颜色0,所以造成有些测试过不了  回复  更多评论
  


只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理