[SMOJ1833]小球染色

本文通过SMOJ1833题目的解析,介绍了线段树这一数据结构在区间修改和单点查询问题中的应用。线段树作为一种高效的辅助手段,能够在O(logN)的时间复杂度内完成操作。文章强调了数据结构在提升程序效率中的重要性,并解释了线段树中打散标记(lazy-tag)的概念,以及如何利用线段树解决颜色统一的区间问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这题很明显,就是要实现两种操作:区间修改和单点查询,是典型的数据结构裸题,可以用线段树解决。

因为题目没有太多好讲的,我们不妨先回顾一下线段树这个数据结构,最后再稍来点拨一下本题。
首先还是要回到本质,数据结构到底是什么?为什么 OI 的学习越到后面,要花一百多甚至几百行去编写一个数据结构?

我们来看 wikipedia 对数据结构的定义,“In computer science, a data structure is a particular(特殊的) way of organizing data in a computer so that it can be used efficiently(有效地).”
由此可见,数据结构是一种强有力的辅助手段,帮助我们更好地处理我们所拥有的数据,或在运算时大幅度提高效率。
效率这个词,在 OI 竞赛中实在是太重要了。因此也就引发了 OI 选手对数据结构的追求,以至于早就有人提出“程序=算法+数据结构”的概念。

再回到线段树来看。线段树是处理区间问题时的利器,可以参考 04 年国家集训队有两篇论文对此作了较详细的阐述。无论是单点修改、区间查询,区间修改、单点查询……线段树都可以在 O(logN) 内解决我们的需求,而能让他如此高效的“秘诀”就在于打散标记(lazy-tag)。

对一棵线段树的操作可以看成对一条线段的操作,其中每个结点对应管辖原线段中的一小段,每个结点要么没有儿子(叶子结点),要么有两个儿子。
当我们把一个区间修改之后,如果一层一层递归下去再修改儿子,显然是非常慢的,不能够符合我们对数据结构的需求,于是 lazy-tag 思想应运而生。

其精华就是:反正我只有在需要查询的时候才需要知道某一段最新的值,那么之前如果修改了某一段,就先“置之不理”,在需要进行递归的时候(无论是 update 还是 query 都好),顺便把标记打散往下带。这样一来效率就可以得到飞跃。

事实上,利用线段树解决问题,关键在于每个结点要记录什么值?
对于此题,其实就是一段区间是否为统一的颜色的问题。如果某段区间的颜色统一,那么就可以往下传。虽然可以另外加 bool 变量标记,但是把区间的颜色值赋为 0 也是一种可行的方法,毕竟题目中合法涂色是不可能出现 0 的。

时间复杂度:O(nlog2n)

参考代码:

#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

using namespace std;

const int maxn = 2e5 + 100;

struct SegmentTree { //写成结构体的形式方便操作
    struct Tnode {
        int l, r;
        int lch, rch;
        int val;
        Tnode () { lch = rch = val = 0; }
    } tree[maxn << 2];

    int freePoint;
    SegmentTree () { freePoint = 0; }

    int getPoint(int l, int r) { //动态分配法,更灵活,避免造成浪费
        int root = ++freePoint;
        tree[root].l = l;
        tree[root].r = r;
        return root;
    }

    int build(int l, int r) {
        int root = getPoint(l, r);
        if (l < r) {
            int mid = (l + r) >> 1;
            tree[root].lch = build(l, mid);
            tree[root].rch = build(mid + 1, r);
        }
        return root;
    }

    void pushDown(int root) { //打散
        if (tree[root].val) {
            tree[tree[root].lch].val = tree[root].val;
            tree[tree[root].rch].val = tree[root].val;
            tree[root].val = 0;
        }
    }

    void update(int l, int r, int color, int root) {
        Tnode& node = tree[root];
        if (node.r < l || r < node.l) return;
        if (l <= node.l && node.r <= r) {
            tree[root].val = color;
            return;
        }
        pushDown(root);
        update(l, r, color, node.lch);
        update(l, r, color, node.rch);
    }

    int queryOne(int pos, int root) {
        Tnode& node = tree[root];
        if (node.l == pos && node.r == pos) return node.val;
        pushDown(root);
        if (pos <= tree[node.lch].r) return queryOne(pos, node.lch); else return queryOne(pos, node.rch);
    }

} SegT;

int n, m;

int main(void) {
    freopen("1833.in", "r", stdin);
    freopen("1833.out", "w", stdout);
    scanf("%d%d", &n, &m);
    int root = SegT.build(1, n);
    for (int i = 0; i < m; i++) {
        int order;
        scanf("%d", &order);
        if (order == 1) {
            int a, b, c;
            scanf("%d%d%d", &a, &b, &c);
            SegT.update(a, b, c, root);
        }
        if (order == 2) {
            int x;
            scanf("%d", &x);
            printf("%d\n", SegT.queryOne(x, root));
        }
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值