算法竞赛进阶指南-第五章-线段树

算法竞赛进阶指南-第五章-线段树

- 11 mins

一种基于分支思想的二叉树结构.与树状数组相比,线段树是一种更加通用的结构

image-20230307173144427

image-20230307173210574

保存线段树的数组长度要不小于4N才能保证不会越界

线段树的基本用途是对序列进行维护,支持查询与修改指令

线段树作为一种比较通用的数据结构,能够维护各式各样的信息,前提是这些信息容易按照区间进行划分和合并(又称满足区间可加性)

建立一颗线段树并在每个结点上保存对应区间的最大值的代码

struct SegmentTree{
    int l, r;
    int dat;
} t[SIZE * 4];//struct 数组存储线段树
void build(int p, int l, int r)
{
    t[p].l = l, t[p].r = r;//结点p代表区间[l,r]
    if(l == r) { t[p].dat = a[l]; return;}//叶节点
    int mid = (l +r) /2;//折半
    build(p * 2, l, mid);//左子节点[l,mid],编号p*2
    build(p * 2 + 1, mid + 1, r);//右子节点[mid+1,r],编号p*2+1
    t[p].dat = max(t[p*2].dat, t[p*2+1].dat);//从下往上传递信息(此处传递的是最大值信息,具体可根据问题修改)
}
build(1,1,n);//调用入口

线段树的单点修改 O(logN)

从根节点出发,递归找到代表区间[x,x]的叶节点,然后从下往上更新[x,x]以及它的所有祖先节点上保存的信息

void change(int p, int x, int v)
{
    if(t[p].l == t[p].r){t[p].dat = v; return;}//找到叶节点
    int mid = (t[p].l + t[p].r) /2;
    if(x <= mid) change(p*2, x, v);//x属于左半区间
    else change(p * 2 + 1, x, v);//x属于右半区间
    t[p].dat = max(t[p*2].dat, t[p*2+1].dat);//从下往上更新信息
}
change(1,x,v);//调用入口

线段树的区间查询 O(logN)

image-20230307192923417

int ask(int p, int l, int r)
{
    if(l <= t[p].l && r >= t[p].r) return t[p].dat;//完全包含
    int mid = (t[p].l + t[p].r) / 2;
    int val = -(1<<30);//负无穷大
    if(l <= mid) val = max(val, ask(p*2, l, r));//左子节点有重叠(此处为最大值信息)
    if(r > mid) val = max(val, ask(p*2+1, l, r));//右子节点有重叠(此处为最大值信息)
    return val;
}
cout << ask(1,l,r) << '\n';//调用入口
Can you answer on these queries III

典型的线段树问题,把上面几个函数套入,维护区间和sum,区间最大连续子段和dat,紧靠左端的最大连续子段和lmax,紧靠右端的最大连续子段和rmax

完善build,change和ask函数中传递和取得的信息

image-20230308112416882

#include <bits/stdc++.h>
using namespace std;
struct SegmentTree
{
    int l, r;
    long long dat;
    long long sum;
    long long lmax;
    long long rmax;
};
SegmentTree t[2000005]; // struct 数组存储线段树
int n, m;
int a[500005];
void buildd(int p, int l, int r)
{
    t[p].l = l, t[p].r = r;
    if (l == r)
    {
        t[p].dat = a[l];
        t[p].sum = a[l];
        t[p].lmax = a[l];
        t[p].rmax = a[l];
        return;
    }
    int mid = (l + r) / 2;
    buildd(p * 2, l, mid);
    buildd(p * 2 + 1, mid + 1, r);
    t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
    t[p].lmax = max(t[p * 2].lmax, t[p * 2].sum + t[p * 2 + 1].lmax);
    t[p].rmax = max(t[p * 2 + 1].rmax, t[p * 2 + 1].sum + t[p * 2].rmax);
    t[p].dat = max(t[p * 2].dat, t[p * 2 + 1].dat);
    t[p].dat = max(t[p].dat, t[p * 2].rmax + t[p * 2 + 1].lmax);
}
void changee(int p, int x, int v)
{
    if (t[p].l == t[p].r)
    {
        t[p].dat = v;
        t[p].sum = v;
        t[p].lmax = v;
        t[p].rmax = v;
        return;
    }
    int mid = (t[p].l + t[p].r) / 2;
    if (x <= mid)
        changee(p * 2, x, v);
    else
        changee(p * 2 + 1, x, v);
    t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
    t[p].lmax = max(t[p * 2].lmax, t[p * 2].sum + t[p * 2 + 1].lmax);
    t[p].rmax = max(t[p * 2 + 1].rmax, t[p * 2 + 1].sum + t[p * 2].rmax);
    t[p].dat = max(t[p * 2].dat, t[p * 2 + 1].dat);
    t[p].dat = max(t[p].dat, t[p * 2].rmax + t[p * 2 + 1].lmax);
}
SegmentTree ask(int p, int l, int r)
{
    if (l <= t[p].l && r >= t[p].r)
        return t[p];
    int mid = (t[p].l + t[p].r) / 2;
    SegmentTree val;
    if (l <= mid && r > mid)
    {
        SegmentTree t1, t2;
        t1 = ask(p * 2, l, r), t2 = ask(p * 2 + 1, l, r);
        val.lmax = max(t1.lmax, t1.sum + t2.lmax);
        val.rmax = max(t2.rmax, t2.sum + t1.rmax);
        val.dat = max(t1.dat, t2.dat);
        val.dat = max(val.dat, t1.rmax + t2.lmax);
    }
    else if (l <= mid)
        val = ask(p * 2, l, r);
    else if (r > mid)
        val = ask(p * 2 + 1, l, r);
    return val;
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= n; i++)
        cin >> a[i];
    buildd(1, 1, n);
    while (m--)
    {
        int tt, x, y;
        cin >> tt >> x >> y;

        if (tt == 1)
        {
            if (x > y)
                swap(x, y);
            cout << ask(1, x, y).dat << '\n';
        }
        else
            changee(1, x, y);
    }
}
Interval GCD

写了一两个小时,没写出来,不知道哪的问题,写法应该是对的,过了2/7的数据

gcd(x,y,z) = gcd(x,y-x,z-y);

构造一个差分序列,用线段树维护其区间最大公约数

构建树状数组维护A数组

image-20230308173952309

延迟标记

除了在修改指令中直接划分成的O(logN)歌结点之外,对任意结点的修改都延迟到”在后续操作中递归进入它的父节点”时再进行.这样一来,每条查询或修改指令的时间复杂度都降低到了O(logN)

image-20230308193704535

image-20230308193855075

image-20230308193901887

扫描线