线段树
来源:互联网 发布:大金鹅复制软件 编辑:程序博客网 时间:2024/06/05 05:38
首先我们来看一下线段树的定义,线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。
使用线段树可以快速的查找某一个节点在若干条线段中出现的次数,时间复杂度为O(logN)。
线段树问题可以大致分为以下四类
1.单点更新:最简单最基础的线段树问题,思想是只更新叶子节点,然后回溯更新他的父亲节点.
2.区间更新:一段区间内的数据一起更改,需要用到延迟标记(刚开始会难理解,最好是跟着代码人肉走一遍),
3.区间合并:询问区间中满足条件的连续最长区间是的多少,回溯的时候需要对左右儿子的区间进行合并(hdu 1540)
4.扫描线问题:最典型的就是矩形面积并,周长并等题(poj 1151)
首先看单点更新问题,hdu 1166 :http://acm.split.hdu.edu.cn/showproblem.php?pid=1166
题目意思就是:给你n个数字,分别代表这n个点最初有的人数,随后可以对这n个点当中的一点进行增加或减少操作,并且还要查询某段区间的总和.线段树一般变化最大的就是询问,和更新,所以,大家只要把询问和更新给弄明白了,这题也就明白了,还有,线段树对递归要求有点高,递归不是很熟的同学,最好去把递归复习几遍,附上hdu1166的AC代码:
#include<stdio.h>#include<string.h>#define maxn 50000int ans;struct node{ int left, right, sum; int mid()//求中值 { return (left + right) >> 1; }}tree[maxn * 4];//建树[left,right],rt为节点序号void btree(int left, int right, int rt){ tree[rt].left = left; tree[rt].right = right; if (left == right) { scanf("%d", &tree[rt].sum); return; } int mid = tree[rt].mid(); btree(left, mid, rt << 1); btree(mid + 1, right, rt << 1 | 1); tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;}//在[left,right]区间内查询[L,R]区间的总数void query(int left, int right, int rt, int L, int R){ if (L <= left && right <= R) { ans += tree[rt].sum; return; } int mid = tree[rt].mid(); if (R <= mid)//R在[left,mid]区间内 query(left, mid, rt << 1, L, R); else if (L > mid)//L在[mid +1,right]区间内 query(mid + 1, right, rt << 1 | 1, L, R); else { query(left, mid, rt << 1, L, R); query(mid + 1, right, rt << 1 | 1, L, R); }}//在[left,right]区间内,叶子节点为左右儿子都为pos的点的sum值加addvoid update(int left, int right, int rt, int pos, int add){ if (left == right) { tree[rt].sum += add; return; } int mid = tree[rt].mid(); if (pos <= mid)//pos值在[left,mid]区间内,则一直递归,直到叶子节点 update(left, mid, rt << 1, pos, add); else update(mid + 1, right, rt << 1 | 1, pos, add); tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum;}int main(){ int t, n, cnt; int a, b; char str[10]; cnt = 1; scanf("%d", &t); while (t--) { scanf("%d", &n); btree(1, n, 1); printf("Case %d:\n", cnt++); while (scanf("%s", str)) { if (str[0] == 'E') break; scanf("%d%d", &a, &b); if (str[0] == 'Q') { ans = 0; query(1, n, 1, a, b); printf("%d\n", ans); } else if (str[0] == 'A') update(1, n, 1, a, b); else update(1, n, 1, a, -b); } } return 0;}//1//10//1 2 3 4 5 6 7 8 9 10//Query 6 6//Add 6 -3//Query 6 6
第二个问题是区间更新的问题,这里涉及一个更新延迟的问题,那么问题来了,什么是更新延迟呢?为什么要用到更新延迟呢?要怎么使用更新延迟呢?
我们先解决第二个疑问,为了使所有sum值都保持正确,每一次插入操作可能要更新O(N)个sum值,从而使时间复杂度退化为O(N)。为了降低时间复杂度,我们引入了”更新延迟”.回到第一个问题,更新延迟的思想就是先在结点上做标记,而并非真正执行,直到根据查询操作的需要分成两部分时再执行更新操作。怎么使用详见代码.
现在,我们引入一个例题来感受一下更新延迟..poj3468
题目链接:http://poj.org/problem?id=3468
题目大意:给你n个数字,分别代表这n个点最初有的人数,随后可以对这n个点当中的某段区间进行增加操作,并且还要查询某段区间的总和.
#include <iostream>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;typedef long long ll;const int maxn = 100000;int t, n, q;ll anssum;struct node { ll l, r; ll addv, sum;}tree[maxn << 2];//由子节点递归回来,修改父节点中的信息void maintain(int id){ if (tree[id].l >= tree[id].r) return; tree[id].sum = tree[id << 1].sum + tree[id << 1 | 1].sum;}//建树[l,r],id为节点序号void build(int id, ll l, ll r){ tree[id].l = l; tree[id].r = r; tree[id].addv = 0; tree[id].sum = 0; if (l == r) { tree[id].sum = 0; return; } ll mid = (l + r) >> 1; build(id << 1, l, mid); build(id << 1 | 1, mid + 1, r); maintain(id);}//更新延迟:如果id节点有增量,更新其子节点的信息,如果id节点为叶子节点或无增量则不做任何操作void pushdown(int id){ if (tree[id].l >= tree[id].r) return; if (tree[id].addv) { ll tmp = tree[id].addv; tree[id << 1].addv += tmp; tree[id << 1 | 1].addv += tmp; tree[id << 1].sum += (tree[id << 1].r - tree[id << 1].l + 1)*tmp; tree[id << 1 | 1].sum += (tree[id << 1 | 1].r - tree[id << 1 | 1].l + 1)*tmp; tree[id].addv = 0;//别忘了把这个节点恢复为初始化时候的状态(加过了以后就不能再加了) }}//更新操作:从第id个节点开始,区间[l,r]内的数加valvoid updateAdd(int id, ll l, ll r, ll val){ if (tree[id].l >= l && tree[id].r <= r) { tree[id].addv += val; tree[id].sum += (tree[id].r - tree[id].l + 1)*val; return; } pushdown(id); ll mid = (tree[id].l + tree[id].r) >> 1; //继续更新其子区间 if (l <= mid) updateAdd(id << 1, l, r, val); if (mid < r) updateAdd(id << 1 | 1, l, r, val); maintain(id);}//从第id个节点开始,询问[l,r]区间内所有值之和void query(int id, ll l, ll r){ if (tree[id].l >= l && tree[id].r <= r) { anssum += tree[id].sum; return; } pushdown(id); ll mid = (tree[id].l + tree[id].r) >> 1; if (l <= mid) query(id << 1, l, r); if (mid < r) query(id << 1 | 1, l, r); maintain(id);}int main(){ ll a[maxn]; scanf("%d%d", &n, &q); build(1, 1, n); for (int i = 1;i <= n;i++) { scanf("%lld", &a[i]); updateAdd(1, i, i, a[i]); } while (q--) { char qu; ll a, b, c; scanf(" %c",&qu);//%c前要有空格 if (qu == 'C') { scanf("%lld%lld%lld", &a, &b, &c); updateAdd(1, a, b, c); } else { scanf("%lld%lld", &a, &b); anssum = 0; query(1, a, b); printf("%lld\n", anssum); } } return 0;}
接下来是第三类问题,区间合并,先看一个模板题,hdu3911
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3911
题意:有白石和黑石两种石头
1.输入1,x,y,表示把[x, y]里面的黑改为白,白改为黑。
2.输入2,x,y,表示查询从左边起连续黑石的数量的最大值。
思路:用1标志黑石,0标志白石。
#include <iostream>#include <cstdio>#include <stack>#include <algorithm>using namespace std;#define lrt rt << 1#define rrt rt << 1 | 1typedef struct Node { int l, r, v;//v标记是否被覆盖 int lone, sone, rone;//lone表示从最左边数连续1的长度,rone表示从右边数连续1的长度,sone表示连续最长的1的个数 int lzero, szero, rzero;//lzero表示从左边数连续0的长度,rzero表示从右边数连续0的长度,szero表示连续最长的0的个数}Node;const int maxn = 500005;int cmd;int n, q;int x[maxn];Node tree[maxn << 2];void pushUP(int rt, int len) { tree[rt].lone = tree[lrt].lone; tree[rt].rone = tree[rrt].rone; tree[rt].lzero = tree[lrt].lzero; tree[rt].rzero = tree[rrt].rzero; if (tree[rt].lone == len - len / 2) tree[rt].lone += tree[rrt].lone; if (tree[rt].rone == len / 2) tree[rt].rone += tree[lrt].rone; if (tree[rt].lzero == len - len / 2) tree[rt].lzero += tree[rrt].lzero; if (tree[rt].rzero == len / 2) tree[rt].rzero += tree[lrt].rzero; tree[rt].sone = max(tree[lrt].sone, tree[rrt].sone); tree[rt].sone = max(tree[rt].sone, tree[lrt].rone + tree[rrt].lone); tree[rt].szero = max(tree[lrt].szero, tree[rrt].szero); tree[rt].szero = max(tree[rt].szero, tree[lrt].rzero + tree[rrt].lzero);}void pushDOWN(int rt) { if (tree[rt].v) { tree[rt].v = 0; tree[lrt].v = !tree[lrt].v; tree[rrt].v = !tree[rrt].v; swap(tree[lrt].lone, tree[lrt].lzero); swap(tree[lrt].rone, tree[lrt].rzero); swap(tree[lrt].sone, tree[lrt].szero); swap(tree[rrt].lone, tree[rrt].lzero); swap(tree[rrt].rone, tree[rrt].rzero); swap(tree[rrt].sone, tree[rrt].szero); }}void build(int l, int r, int rt) { tree[rt].l = l; tree[rt].r = r; tree[rt].v = 0; if (l == r) { tree[rt].lone = tree[rt].rone = tree[rt].sone = (x[l] == 0) ? 0 : 1; tree[rt].lzero = tree[rt].rzero = tree[rt].szero = (x[l] == 1) ? 0 : 1; return; } int mid = (l + r) >> 1; build(l, mid, lrt); build(mid + 1, r, rrt); pushUP(rt, r - l + 1);}void update(int L, int R, int rt) { if (L <= tree[rt].l && tree[rt].r <= R) { swap(tree[rt].lone, tree[rt].lzero); swap(tree[rt].rone, tree[rt].rzero); swap(tree[rt].sone, tree[rt].szero); tree[rt].v = !tree[rt].v; return; } pushDOWN(rt); int mid = (tree[rt].l + tree[rt].r) >> 1; if (L <= mid) update(L, R, lrt); if (mid < R) update(L, R, rrt); pushUP(rt, tree[rt].r - tree[rt].l + 1);}int query(int L, int R, int rt) { if (L == tree[rt].l && R == tree[rt].r) return tree[rt].sone; pushDOWN(rt); int mid = (tree[rt].l + tree[rt].r) >> 1; if (mid >= R) return query(L, R, lrt); else if (mid + 1 <= L) return query(L, R, rrt); else { int tmp = max(query(L, mid, lrt), query(mid + 1, R, rrt)); tmp = max(tmp, min(tree[lrt].rone, mid - L + 1) + min(tree[rrt].lone, R - mid)); return tmp; }}int main() { //freopen("in", "r", stdin); int a, b; while (~scanf("%d", &n)) { for (int i = 1; i <= n; i++) scanf("%d", &x[i]); build(1, n, 1); scanf("%d", &q); while (q--) { scanf("%d %d %d", &cmd, &a, &b); if (cmd == 0) printf("%d\n", query(a, b, 1)); else update(a, b, 1); } } return 0;}
- 线段树?线段树!
- 线段树?线段树!
- 线段_线段树
- 线段_线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- 线段树
- Mapreduce算法二、数据去重(HashSet)
- POJ 3190 Stall Reservations
- Javascript面试题:如何给一个Javascript的对象属性赋值?如何取得属性值?
- 防止XSS攻击,过滤代码
- Part 61 - Named sections in layout files in mvc
- 线段树
- Java路径问题
- NYOJ - 91 - 阶乘之和(贪心算法)
- 通用定时器-输入捕获实验
- Andorid Support Design库 新控件 (下)
- 求sql语句 想要输出 姓名相同,身份证号码不同的记录
- Logstash插件--input file
- sprintf()
- javascript学习笔记—判断值和函数的类型