算法竞赛进阶指南——0x43【线段树】


在这里插入图片描述

基本操作

支持单点更新、区间查询等等操作
凡是树状数组能做的,线段树都能做
在这里插入图片描述

牛客一个小栗子Can you answer these queries III

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 5e5 + 100;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31) - 1;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, k, m, x, y;
ll a[N];
struct tree
{
    ll l, r;
    ll sum, lsum, rsum, ans;
} t[N * 4];
void update(tree &w, tree &l, tree &r)//
{
    w.sum = l.sum + r.sum;
    w.lsum = max(l.lsum, l.sum + r.lsum); //
    w.rsum = max(r.rsum, r.sum + l.rsum);
    w.ans = max(l.rsum + r.lsum, max(l.ans, r.ans));
}
void build(ll w, ll l, ll r)
{
    if (l == r)
        t[w] = {l, r, a[l], a[l], a[l], a[l]};
    else
    {
        t[w] = {l, r};
        ll mid = l + r >> 1;
        build(w << 1, l, mid);
        build(w << 1 | 1, mid + 1, r);
        update(t[w], t[w << 1], t[w << 1 | 1]); //
    }
}
void calc(ll w, ll x, ll v)
{
    if (t[w].l == x && t[w].r == x)
        t[w] = {x, x, v, v, v, v};
    else
    {
        ll mid = t[w].l + t[w].r >> 1;
        if (x <= mid)
            calc(w << 1, x, v);
        else
            calc(w << 1 | 1, x, v);
        update(t[w], t[w << 1], t[w << 1 | 1]);
    }
}
tree query(ll w, ll l, ll r)
{
    if (t[w].l >= l && t[w].r <= r)
        return t[w];
    else
    {
        ll mid = t[w].l + t[w].r >> 1;
        if (r <= mid)
            return query(w << 1, l, r);
        else if (mid < l)
            return query(w << 1 | 1, l, r);
        else
        {
            tree L = query(w << 1, l, r);
            tree R = query(w << 1 | 1, l, r);
            tree res;
            update(res, L, R);
            return res;
        }
    }
}
int main()
{
    // IOS;
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    build(1, 1, n); //
    while (m--)
    {
        scanf("%lld%lld%lld", &k, &x, &y);
        if (k == 1)
        {
            if (x > y)
                swap(x, y);
            printf("%lld\n", query(1, x, y).ans);
        }
        else if (k == 2)
            calc(1, x, y);
    }
    return 0;
}

牛客一道小栗子interval GCD

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 500000;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31) - 1;
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, m, l, r, d;
ll a[N], b[N], c[N];
struct wt
{
    ll l, r, dat;
} t[N * 4];//4倍保证不越界
ll gcd(ll x, ll y) { return y == 0 ? x : gcd(y, x % y); }
ll lowbit(ll x) { return x & -x; }
void add(ll x, ll val)
{
    for (x; x <= n; x += lowbit(x))
        c[x] += val;
}
ll getsum(ll x)
{
    ll res = 0;
    for (; x; x -= lowbit(x))
        res += c[x];
    return res;
}
void build(ll u, ll l, ll r)//建树
{
    t[u].l = l, t[u].r = r;
    if (l == r)
    {
        t[u].dat = b[r]; //
        return;
    }
    ll mid = l + r >> 1; //建树有问题
    build(u * 2, l, mid);
    build(u * 2 + 1, mid + 1, r);
    t[u].dat = gcd(t[u * 2].dat, t[u * 2 + 1].dat);//从下往上传递信息
}
void change(ll u, ll x, ll v)//单点修改
{
    if (t[u].l == t[u].r)
    {
        t[u].dat += v;
        return;
    }
    ll mid = t[u].l + t[u].r >> 1;
    if (x <= mid) //
        change(u * 2, x, v);
    else
        change(u * 2 + 1, x, v);
    t[u].dat = gcd(t[u * 2].dat, t[u * 2 + 1].dat);//从下往上更新信息
}
ll ask(ll u, ll l, ll r)//区间查询
{
    if (l <= t[u].l && t[u].r <= r)//完全包含类型
        return abs(t[u].dat);
    ll mid = t[u].l + t[u].r >> 1;
    ll res = 0;
    if (l <= mid)
        res = gcd(res, ask(u * 2, l, r)); //gcd的扩展
    if (mid < r)
        res = gcd(res, ask(u * 2 + 1, l, r)); //
    return abs(res);
}
int main()
{
    // IOS;
    char ch[2];
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i]);
        b[i] = a[i] - a[i - 1];
    }
    build(1, 1, n);//切记写上调用入口
    while (m--)
    {
        scanf("%s", ch);
        scanf("%lld%lld", &l, &r);
        if (ch[0] == 'C')
        {
            scanf("%lld", &d);
            change(1, l, d);
            if (r < n)
                change(1, r + 1, -d);
            add(l, d);
            add(r + 1, -d);
        }
        else if (ch[0] == 'Q')
        {
            ll val = a[l] + getsum(l);
            ll res = l < r ? ask(1ll, l + 1, r) : 0; //
            printf("%lld\n", gcd(val, res));
        }
    }
    return 0;
}

延迟标记

牛客一道小栗子A Simple Problem with Integers
上次用的树状数组做的,这次用线段树+延迟标记来做

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 100000;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31);
const int pi = acos(-1);
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, q, l, r, d;
char ch;
ll a[N];
struct segment//线段树
{
    ll l, r, sum, add;
} t[N * 4];
void build(ll p, ll l, ll r)//建树
{
    t[p].l = l, t[p].r = r;
    if (l == r)
    {
        t[p].sum = a[r];
        return;
    }
    ll mid = t[p].l + t[p].r >> 1;
    build(p * 2, l, mid);
    build(p * 2 + 1, mid + 1, r);
    t[p].sum = t[p * 2].sum + t[p * 2 + 1].sum;
}
void spread(ll p)//延迟标记
{
    if (t[p].add)
    {
        t[p * 2].sum += (t[p * 2].r - t[p * 2].l + 1) * t[p].add;
        t[p * 2 + 1].sum += (t[p * 2 + 1].r - t[p * 2 + 1].l + 1) * t[p].add;
        t[p * 2].add += t[p].add;
        t[p * 2 + 1].add += t[p].add;
        t[p].add = 0;
    }
}
void change(ll p, ll l, ll r, ll d)//单点修改
{
    if (l <= t[p].l && t[p].r <= r)
    {
        t[p].sum += (t[p].r - t[p].l + 1) * d;
        t[p].add += d;
        return;
    }
    spread(p); //
    ll mid = t[p].l + t[p].r >> 1;
    if (l <= mid)
        change(p * 2, l, r, d);
    if (mid < r) ///两个子树都要过一遍,所以不能是else if
        change(p * 2 + 1, l, r, d);
    t[p].sum = (t[p * 2].sum + t[p * 2 + 1].sum); //
}
ll ask(ll p, ll l, ll r)//区间查询
{
    if (l <= t[p].l && t[p].r <= r)
        return t[p].sum;
    spread(p);//没有完全包含p,需要向下递归,所以延迟标记起作用了
    ll mid = t[p].l + t[p].r >> 1;
    ll res = 0;
    if (l <= mid)
        res += ask(p * 2, l, r);
    if (mid < r) //两个子树都要过一遍,所以不能是else if
        res += ask(p * 2 + 1, l, r);
    return res;
}
int main()
{
    // IOS;
    scanf("%lld%lld", &n, &q);

    for (int i = 1; i <= n; i++)
        scanf("%lld", &a[i]);
    build(1, 1, n);
    while (q--)
    {
        scanf(" %c%lld%lld", &ch, &l, &r);//scanf()之前加空格
        if (ch == 'Q')
            printf("%lld\n", ask(1, l, r));
        else
        {
            scanf("%lld", &d);
            change(1, l, r, d);
        }
    }
    return 0;
}

扫描线算法

只要找到对应结点的区间能完全覆盖当前线段区间就可以回溯统计了,并不需要更新到叶子节点,这是线段树为什么效率高的原因

线段树有一些细节:

  • 要注意因为扫描线算法中线段树维护的是线段长度而不是点权,所以需要做出改变;[l,r] 维护的是xr + 1 −xl
  • 因为线段树右端点的意义已经变化了,所以线段树只需要维护cnt− 1 个元素(cnt为x数组离散化后的元素个数)
  • 写pushup时要注意叶子节点没有儿子需要特判,否则会数组越界

最终的答案就是线段树根节点维护的线段长度乘上当前线段与下一条线段的高度差

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 1e5 + 10;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31);
const int pi = acos(-1);
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, tot, T;
vector<double> yy;
struct segment
{
    ll l, r, cnt;
    double len; //
} t[N * 4 * 2];
struct line
{
    double x, yl, yr;
    ll f;
    bool operator<(const line &t) const { return x < t.x; }
} e[N * 2];
void build(ll p, ll l, ll r)
{
    t[p] = {l, r, 0, 0};
    if (l == r)
        return;
    else //
    {
        ll mid = l + r >> 1;
        build(p * 2, l, mid);
        build(p * 2 + 1, mid + 1, r);
    }
}
void pushup(ll p)
{
    if (t[p].cnt)
        t[p].len = (yy[t[p].r + 1] - yy[t[p].l]); //
    else if (t[p].l != t[p].r)
        t[p].len = t[p * 2].len + t[p * 2 + 1].len;
    else //特判没有儿子的情况
        t[p].len = 0;
}
void updata(ll p, ll l, ll r, ll k)
{//l,r为扫描线两端
    if (l <= t[p].l && t[p].r <= r)
    {
        t[p].cnt += k;
        pushup(p); //分情况求出结点的len
    }
    else //
    {
        ll mid = t[p].l + t[p].r >> 1;
        if (l <= mid)
            updata(p * 2, l, r, k);
        if (mid + 1 <= r)
            updata(p * 2 + 1, l, r, k); //
        pushup(p);                      
    }
}
int main()
{
    // IOS;
    while (scanf("%lld", &n) && n)
    {
        double x1, x2, y1, y2;
        double res = 0.0;
        yy.clear(),tot=0;//mlgb,tot记得初始化,不然会数组越界
        for (int i = 1; i <= n; i++)
        {
            scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2);
            e[++tot] = {x1, y1, y2, 1};
            yy.push_back(y1);
            e[++tot] = {x2, y1, y2, -1};
            yy.push_back(y2);
        }
        sort(e + 1, e + 1 + 2 * n);
        sort(yy.begin(), yy.end());
        yy.erase(unique(yy.begin(), yy.end()), yy.end());
        // for (int i = 1; i <= pos; i++)
        //     printf("%lf\n", yy[i]);
        build(1, 0, yy.size() - 2);//维护的是线段,所以建树从0~size()-1-1
        for (int i = 1; i <= 2 * n; i++)//总共是2*n个x坐标,被分成了2*n-1段
        {
            if (i > 1)//第一个坐标不做运算
                res += t[1].len * (e[i].x - e[i - 1].x);
            //在离散化的坐标里面找到相等时的位置下标
            ll yl = lower_bound(yy.begin(), yy.end(), e[i].yl) - yy.begin();
            ll yr = lower_bound(yy.begin(), yy.end(), e[i].yr) - yy.begin();
            updata(1, yl, yr - 1, e[i].f);//这里右端传入的是yr-1,不是yr
        }
        printf("Test case #%lld\nTotal explored area: %.2lf\n\n", ++T, res);
    }
    return 0;
}

牛客一道小栗子Stars in Your Window

#pragma GCC optimize(3, "Ofast", "inline")
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
typedef pair<long long, long long> pll;
const int N = 10000 + 100;
const int M = 2e6 + 1000;
const int inf = 0x3f3f3f3f;
const int mod = (1 << 31);
const int pi = acos(-1);
#define IOS                  \
    ios::sync_with_stdio(0); \
    cin.tie(0);              \
    cout.tie(0);
ll n, w, h, tot, res;
ll yy[N * 2]; //
struct segment
{
    ll l, r, dat, lazy;
} t[N * 8];
struct edge
{
    ll x, yl, yr, f;
    bool operator<(const edge &t) const { return x < t.x; }
} e[N * 4];
void build(ll p, ll l, ll r)//建树
{
    t[p] = {l, r, 0, 0};
    if (l == r)
        return;
    ll mid = l + r >> 1;
    build(p * 2, l, mid);
    build(p * 2 + 1, mid + 1, r);
}
void spread(ll p)//延迟标记
{
    if (t[p].lazy)
    {
        t[p * 2].dat += t[p].lazy;
        t[p * 2 + 1].dat += t[p].lazy;
        t[p * 2].lazy += t[p].lazy;
        t[p * 2 + 1].lazy += t[p].lazy;
        t[p].lazy = 0;
    }
}
void updata(ll p, ll l, ll r, ll val) //l,r是未离散化后的坐标【区间更新】
{
    if (l <= yy[t[p].l] && yy[t[p].r] <= r)
    {
        t[p].dat += val;
        t[p].lazy += val; //
        return;
    }
    spread(p);//记得要延迟标记哦
    ll mid = t[p].l + t[p].r >> 1;
    if (r <= yy[mid])
        updata(p * 2, l, r, val);
    else if (yy[mid] <= l)
        updata(p * 2 + 1, l, r, val);
    else
    {
        updata(p * 2, l, yy[mid], val);
        updata(p * 2 + 1, yy[mid + 1], r, val); //
    }
    t[p].dat = max(t[p * 2].dat, t[p * 2 + 1].dat);
}
int main()
{
    // IOS;
    while (scanf("%lld%lld%lld", &n, &w, &h) == 3) //scanf()返回的是输入变量的个数
    {
        res = 0,tot=0;//能不能记得初始化?
        ll x, y, z;
        for (int i = 1; i <= n; i++)
        {
            scanf("%lld%lld%lld", &x, &y, &z);
            e[++tot] = {x, y, y + h - 1, z}; //
            yy[tot] = y;
            e[++tot] = {x + w, y, y + h - 1, -z}; //
            yy[tot] = y + h - 1;
        }
        sort(e + 1, e + 1 + tot);
        sort(yy + 1, yy + 1 + tot);//离散化
        ll pos = unique(yy + 1, yy + 1 + tot) - (yy + 1);//去重
        build(1, 1, pos); //离散化之后的pos
        for (int i = 1; i <= tot; i++)
        {
            updata(1, e[i].yl, e[i].yr, e[i].f);
            res = max(res, t[1].dat); //取最大
        }
        printf("%lld\n", res);
    }
    return 0;
}

动态开点与线段树合并

准备阶段

struct segment
{
    ll lc, rc, dat, l, r;
} t[N * 4];

动态开点

ll build()
{
    ++tot;
    t[tot].lc = t[tot].rc = t[tot].dat = 0;
    return tot;
}

单点更新升级版

void updata(ll p, ll l, ll r, ll x, ll val)
{
    if (l == r)
    {
        t[p].dat += val;
        return;
    }
    ll mid = l + r >> 1;
    if (x <= mid)
    {
        if (!t[p].lc)
        {
            t[p].lc = build();
            updata(t[p].lc, l, mid, x, val);
        }
    }
    else if (mid + 1 <= r)
    {
        if (!t[p].rc)
        {
            t[p].rc = build();
            updata(t[p].rc, mid + 1, r, x, val);
        }
    }
    t[p].dat = max(t[t[p].lc].dat, t[t[p].rc].dat);
}

线段树合并

ll merge(ll p, ll q, ll l, ll r)
{
    if (!p)
        return p;
    if (!q)
        return q;
    if (l == r)
    {
        t[p].dat += t[q].dat;
        return p;
    }
    ll mid = l + r >> 1;
    t[p].lc = merge(t[p].lc, t[q].lc, l, mid);
    t[p].rc = merge(t[p].rc, t[q].rc, mid + 1, r);
    t[p].dat = max(t[t[p].lc].dat, t[t[p].rc].dat);
    return p;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

WTcrazy _

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值