线段树

12 篇文章 0 订阅
2 篇文章 0 订阅

首先我们来看一下线段树的定义,线段树是一种二叉搜索树,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[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 50000
int 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值加add
void 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]内的数加val
void 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 | 1
typedef 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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值