线段树

来源:互联网 发布:大金鹅复制软件 编辑:程序博客网 时间: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;}
0 0
原创粉丝点击