线段树

本文深入探讨线段树算法的原理与应用,包括初始化、查询、更新等核心操作,并通过多个实例讲解如何解决区间查询、区间更新等问题,涵盖了RMQ、区间最值、区间求和、单调栈等多种场景。

线段树

博客推荐:
夜深人静写算法-线段树
线段树详解

线段树一个很大的用途就是用于求解RMQ问题,就是在一个区间中查询最值
线段树分为很多类型有区间最值区间求和,还有单点更新,区间更新。。。

模板:

初始化树:

void init(int id, int start, int end){
	if(start == end){
		node[id].value = input[start];
	}else if(start < end){
		int zhong = (start + end) >>  1;
		int lchild = id << 1;
		int rchild = id << 1 | 1;
		init(lchild, start, zhong);
		init(rchild, zhong+1, end);
		node[id].value = max(node[lchild].value, node[rchild].value);
	}
}


查询:
int query(int id, int start, int end, int left, int right){
	//效率比较低的写法,因为这是等中断后函数入栈了才return
	//if(start>right || end <left) return -1; 
	
	if(start>=left && end<=right) return node[id].value;
	
	if(start < end){
		int zhong = (start + end) >> 1;
		int p1 = -1, p2 = -1;
		if(left <= zhong) p1 = query(id<<1, start, zhong, left, right);
		if(zhong < right) p2 = query(id<<1|1, zhong+1, end, left, right);
		if(p1 == -1) return p2;
		if(p2 == -1) return p1;
		if(p1 > p2) return p1;
		return p2;
	}
	return -1;
}

单点更新:

void update(int id, int start, int end, int index, int val){
	if(start==end && start==index){
		node[id].value += val;
	}else if(start < end){
		int zhong = (start + end) >> 1;
		int lchild = id << 1;
		int rchild = id << 1 | 1;
		if(zhong >= index)
			update(lchild, start, zhong, index, val);
		else
			update(rchild, zhong+1, end, index, val);
		
		node[id].value = max(node[lchild].value, node[rchild].value);
	}
}

单点更新
hdu1754 ---- 区间最值 + 单点更新
hdu1166 ---- 区间求和 + 单点更新
hdu2795 ---- 区间最值 + 单点更新
hdu1394 ---- 这道题我整的久

hdu1394
这道题我开始用线段树还是很死板,本来如果没有学线段树的话,就是O(n3)的复杂度,然后我就只是再最后那用了一哈线段树,还是超时。。。

POJ2452
这道题我开始还是很死板。。。

POJ3368
这道题说实话有点恶心。。。

POJ2823
这道题很烦,我目前还不知道怎么用G++通过,都是用C++才能过,我之前就是一直用G++,一直都TLE,后头在网上看到有人说用C++。。。我说我线段树都已经优化的不能再优化了还是超时,但是看排行榜里面还是有人用G++过了,而且只用了500+ms,太变态了!但是通过这道题我还学到了单调栈、单调队列。。还是有收获



区间更新
单点更新后就是区间更新,区间更新和单点更新的区别就是需要加一个lazy懒惰标记,这是为了不过每次更新一个区间的时候就去挨个更新,而是先把更新的这个信心放在当前要求得一段上,后面如果要继续深入,再将这个lazy交给自己的子节点,提高效率
POJ3468

ZOJ2706
这是我在ZOJ的第一道题,很恼火,紧到WA,我还一直以为是我的延迟标签有点问题,最后才发现是输入long long的时候不能用%I64d,改成cin就对了。。。还有就是感觉ZOJ人好少啊,半天了没得几个人提交,就我一个人紧到在那WA

POJ2828
这道题我觉得还是比较难,也比较好,我深刻体会到了线段树的灵活。我看这道题第一反应就是用链表,或者数组,但是复杂度都是O(n2)。然后就是用才学的线段树,我最早也是用普通的区间更新lazy标记的思路,还想到了一个办法,但是结果都超时了。。让我很郁闷,因为我本来觉得我这个办法还有点溜的。然后我就看的答案,说实话都理解了半天才大概搞懂
推荐这篇博客:POJ2828 Buy Ticket详解
确实这种处理空格个数的思路有点炫

染色问题
HDU1698
这道题虽然说只是个钩子,但是我居然有点小收获。我用我之前的惯用的模板代码超时了,然后参考别人的代码,稍微修改一哈就过了。。
就是这两句。。。

		if(left <= zhong) p1 = query(id<<1, start, zhong, left, right);
		if(right > zhong) p2 = query(id<<1|1, zhong+1, end, left, right);

POJ2528
这道题数据太大,用离散化

POJ2777
这道题如果在查询的时候如果还是每次都深入到根去看有几个不同的颜色就会超时,办法就是夜深人静写算法里面说的,每个线段树结点都要记录到底有哪些颜色,用位运算来解决。。。在这里插入图片描述

HDU5023
刚开始做这道题的时候,由于受前面两道题影响,一上来看到线段树可能稍微有点大,就想到了离散化,结果整了半天发现如果要离散化的话,就不能每次Q的时候的得出结果,必须要等所有输入输入完后才能离散化,然后才输出数据,结果就WA了,然后还是正常操作,结果还是WA几次,后来才发现,他说的默认颜色是2.。。。

矩形面积
还是来个模板,因为有些地方要注意哈:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
#define maxn 205
#define zhong ((start + end) >> 1)
#define lchild (id << 1)
#define rchild (id << 1 | 1)
typedef struct Line{
	double l, r, h;
	int flag;
	Line(double ll, double rr, double hh, int f){
		l = ll; r = rr; h = hh; flag = f;
	}
	Line(){}
	bool operator < (Line const &a) const{
		return h < a.h;
	}
}Line;
typedef struct Node{
	int amount;
	double value;
}Node;
Node node[maxn << 2];
Line line[maxn];
int n;
double shuzu[maxn];

void pushup(int id, int start, int end){
	if(node[id].amount == 0){
		if(start == end-1)
			node[id].value = 0;
		else
			node[id].value = node[lchild].value + node[rchild].value;
	}else{
		node[id].value = shuzu[end] - shuzu[start];
	}
}

void update(int id, int start, int end, int left, int right, int val){
	if(left<=start && end<=right){
		node[id].amount += val;
		pushup(id, start, end);
	}else if(start < end - 1){		
		if(left <= zhong) update(lchild, start, zhong, left, right, val);
		if(right >= zhong) update(rchild, zhong, end, left, right, val);
		pushup(id, start, end);
	}
}

int main(){
	double x1, x2, y1, y2;
	int ji = 1;
	while(~scanf("%d", &n) && n){
		memset(line, 0, sizeof(line));
		//scatter
		int length = 1;
		for(int i = 1; i <= n; i++){
			scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
			line[length] = Line(x1, x2, y1, 1);
			line[length + 1] = Line(x1, x2, y2, -1);
			shuzu[length ++] = x1;
			shuzu[length ++] = x2;
		}		
		sort(shuzu+1, shuzu+length);
		sort(line+1, line+length);
		//remove the same
		int len = 2;
		double qian = shuzu[1];
		for(int i = 2; i < length; i++){
			if(shuzu[i] != qian){
				shuzu[len ++] = shuzu[i];
				qian = shuzu[i];
			}			
		}		

		memset(node, 0, sizeof(node));		
		double answer = 0;
		for(int i = 1; i < length-1; i ++){
			int l = lower_bound(shuzu+1, shuzu+len, line[i].l) - shuzu;
			int r = lower_bound(shuzu+1, shuzu+len, line[i].r) - shuzu;			
			update(1, 1, len-1 , l, r, line[i].flag);
			answer += (line[i+1].h - line[i].h) * node[1].value;
			//这个地方我觉得有点意思,因为刚刚学了扫描线之后,就发现如果是几个矩形并排
			//,那岂不是应该把同一排的矩形一个一个用线段树求总的线段长,然后才能继续往下,
			//求下一排的线段总长,但是这个写法就巧妙的解决的这个问题。
		}		
		printf("Test case #%d\nTotal explored area: %.2f\n\n", ji++, answer);
	}
	return 0;
}

/*
2
10 10 20 20
15 15 25 25.5
2
3 1 23423.34 33
500 2 3430.211 50
0
*/

/*
2
1 1 3 2
5 1 8 2
0
*/

POJ1151
这个题!!!这个岛Atlantis,海王的得嘛~

POJ3277
这道题和上面海王那道题是一样的,但是我还是整了好久,紧到WA,最后发现有个地方写错了。。。

HDU1255
这道题稍微有些变化,但是还好,就变成求cover数量大于等于2的长度之和,但是还要递归向下更新

HDU3265
这道题整的久,想了三个办法,主要开始的办法不好,后头才转过弯。我第一反应就是把每个海报中间的洞下面那条边初始值设为-1,这样扫描线扫上来的时候就会把这个洞的下边从已经有的边去除,但是这就出问题了,因为这不像是之前简单的求面积,之前的求面积都是现有一个1,然后上面才对应着一个-1,但是这里的洞就导致现有-1才有1,所以每次如果更新边以后,都需要递归遍历当前这个边范围内的所有边,然后就一直超时,说的不是很清楚,但是实际写一哈就晓得了,然后我又想了线段树的节点中存两个量,一个是正的一个是负的,负的就代表洞的边,但是这个思路是错的。最后回归简化问题,就是把这个有洞的海报拆成4个矩形,然后就变成了最基础的扫描线+离散化+线段树求矩形面积和。但是我还超了一盘内存,但是稍微调整数组大小后就过了

区间K大数
POJ2104
说实话,用到归并排序我都感觉一般,但是后头查询的时候用二分中的二分确实想不到~
二分中的二分就是先把这整个序列排序,然后用二分枚举,每次枚举一个数都到线段树中查找,查找的是时候就找到了很多子区间,然后对每个子区间就查找有几个数比当前要查的这的数小,就要用二分查找。然后根据然后根据最后查出来的值,在判断是去二分的左区间还是右区间。
下面这张图及时夜深人静写算法里面讲的
在这里插入图片描述
但是这道题我还在网上看到啥子主席树。。反正我这道题就在线段树中动态new数组出来就行了

HDU2665
这道题我遭了两个坑,第一个就是题意,kth big number我觉得应该就是第k大个数,结果我看discuss,都说是第k小个数。。。,还有就是我用的是归并,不是网上很多人说的啥子主席树、划分数,我就是用指针new一个数组出来,然后MLE了,我就用delete,但是哪晓得必须还要把指针置为NULL

#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;

#define zhong ((start + end) >> 1)
#define lchild (id << 1)
#define rchild (id << 1 | 1)
#define ll long long
typedef struct Node{
    ll *shuzu;
    int length;
}Node;
Node node[100010 << 2];
int n, m;
ll input[100010], arr[100010];

void change(int id){
    int length1 = node[lchild].length;
    int length2 = node[rchild].length;
    node[id].shuzu = new ll[length1 + length2 + 2];
    int len = 0, i = 0, j = 0;
    while(i<length1 && j<length2){
        if(node[lchild].shuzu[i] < node[rchild].shuzu[j])
            node[id].shuzu[len ++] = node[lchild].shuzu[i ++];
        else
            node[id].shuzu[len ++] = node[rchild].shuzu[j ++];
    }
    while(i < length1){
        node[id].shuzu[len ++] = node[lchild].shuzu[i ++];
    }
    while(j < length2){
        node[id].shuzu[len ++] = node[rchild].shuzu[j ++];
    }
    node[id].length = len;
}

void init(int id, int start, int end){
    if(start == end){
        node[id].shuzu = new ll[2];
        node[id].shuzu[0] = input[start];
        node[id].length = 1;
    }else if(start < end){
        init(lchild, start, zhong);
        init(rchild, zhong+1, end);
        change(id);
    }
}

int query(int id, int start, int end, int left, int right, ll val){
    if(left<=start && end<=right){
        int index = upper_bound(node[id].shuzu, 
        			node[id].shuzu+node[id].length, val) - node[id].shuzu;
        return index;
    }
    
    if(start < end){
        int p1 = 0, p2 = 0;
        if(left <= zhong) p1 = query(lchild, start, zhong, left, right, val);
        if(right > zhong) p2 = query(rchild, zhong+1, end, left, right, val);
        return p1 + p2;
    }
    
    return 0;
}

int search(int l, int r, int vv, int length){
    int start = 1, end = length, temp;
    while(start < end){
        temp = query(1, 1, n, l, r, arr[zhong]);
        if(temp >= vv){
            end = zhong;
        }else{
            start = zhong + 1; 
        }
    }
    return arr[start];
}

int main(){
    int jishu;
    cin >> jishu;
    while(jishu --){
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i ++) {
        	scanf("%I64d", &input[i]); 
        	arr[i] = input[i];
        }
        sort(arr+1, arr+n+1);
        //remove the same
        int length = 2;
        ll qian = arr[1];
        for(int i = 2; i <= n; i++){
            if(arr[i] != qian){
                arr[length ++] = arr[i];
                qian = arr[i];
            }
        }
//        for(int i = 1; i < length; i++) cout << arr[i] << " "; cout << endl;
        
        init(1, 1, n);        
        int l, r, vv;
        for(int i = 0; i < m; i ++){
            scanf("%d%d%d", &l, &r, &vv);
            cout << search(l, r, vv, length-1) << endl;
        }
        int len = (100005 << 2);
        for(int i = 1; i < len; i++){
            delete [](node[i].shuzu);
            node[i].length = 0;
            node[i].shuzu = NULL;
        }
    }
    return 0;
}

/*
1
9 3
1 2 2 1 2 1 3 3 4
2 3 1
2 3 2
*/

/*
5
10 1 
1 4 2 3 5 6 7 8 9 0 
2 7 2
*/
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值