线段树详解(包含加法线段树、乘法线段树及区间根号线段树,简单易懂)

news2025/1/20 12:04:59

同步发表于洛谷@梦回江南
这一篇文章我们将对线段树中的常规操作进行详细的讨论。

  • 以下所提到的复杂度如无特殊说明均为时间复杂度。 log ⁡ \log log 的底数均为 2 2 2

不开 long long 见祖宗!

文章目录

  • 第一部 普通线段树
    • 一、引入
    • 二、优化方案
    • 三、懒标记(lazytage)
    • 四、Don't talk more, show me the code.
  • 第二部 乘法线段树
    • 一、引入
    • 二、使用lazytage的原理
    • 三、如何使用lazytage
    • 四、Don't talk more, show me the code.
  • 第三部 区间根号线段树
    • 一、引入
    • 二、特殊性
    • 三、如何利用特殊性
    • 四、Don't talk more, show me the code.

第一部 普通线段树

一、引入

对于一串数,举例为 a a a,需要实现以下几种操作(修改操作均为加减法):

  1. 单点修改
  2. 单点查询
  3. 区间修改
  4. 区间查询(区间和)

显然 1 1 1 2 2 2 3 3 3 4 4 4 包含。但为了讨论的清晰,我们也将对其单独讨论。

如果我们用常规方式(数组)存储这串数,其查询操作为 O ( 1 ) O(1) O(1),更改操作为 O ( 1 ) O(1) O(1),求和操作为 O ( l ) O(l) O(l) l l l 为所求序列长度)。

#define LL long long // 不开 long long 见祖宗!
#define ref(i, a, b, p) for (signed(i) = (a); (i) <= signed(b); (i) += signed(p))
const int maxn = 50005;
LL a[maxn], n, q, sum;
void work()
{
    cin >> n;
    ref (i, 1, n, 1)
        cin >> a[i];
    cin >> q;
    ref (__, 1, q, 1)
    {
        int x, k, l, r;
        cin >> x;
        if (x == 1) // 以下均如题
        {
            cin >> k;
            a[x] = k;
        }
        else if (x == 2)
            cout << a[x] << endl;
        else if (x == 3)
        {
            cin >> l >> r >> k;
            ref (i, l, r, 1)
                a[x] += k;
        }
        else
        {
            sum = 0;
            cin >> l >> r;
            ref (i, l, r, 1)
                sum += a[i];
            cout << sum << endl;
        }
    }
}

如果用前缀和来操作的话,虽然求和操作为 O ( 1 ) O(1) O(1),但是更改数值后更新前缀和的复杂度为 O ( n ) O(n) O(n),没有起到太大的优化效果,不再放代码。

有人说 O ( n ) O(n) O(n) 的时间复杂度已经很优了,但是设想,如果有 q q q 次询问,那么总的复杂度为 O ( n q ) O(nq) O(nq),不可接受。

二、优化方案

经过前面优先队列等数据结构的铺垫,我们知道,是一种能在复杂度为 O ( log ⁡ n ) O(\log n) O(logn) 的情况下处理大部分操作的数据结构。
那么我们可以这样想:对于一串数 a a a,我们把它看成一条长度为 n n n 的线段,标记线段左端点为 1 1 1,右端点为 n n n,那么我们让线段上的 1 1 1 对应 a 1 a_1 a1,让 2 2 2 对应 a 2 a_2 a2……让 n n n 对应 a n . a_n. an.,这样我们就用一条线段表示出了 a a a
我们此时建立一棵完全二叉树(偶数个数的情况下为满二叉树),以刚刚建立的线段上的每一个点(从 a 1 a_1 a1 a n a_n an)为叶子节点,用父节点表示其左右儿子的和,那么根节点就为整条线段(也就是整串数)的和。

具体来看(代码放到最后)
建树时,先建立叶子节点,然后逐层回溯计算父结点的值,每个节点需要存储的变量有它的值以及左右端点(就是区间范围)。时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)
如下图,先建立节点 1 , 2 , 3 … 8 1,2,3\dots8 1,2,38(代表 a 1 , a 2 , a 3 … a 8 a_1,a_2,a_3\dots a_8 a1,a2,a3a8,下同),然后逐层回溯更新节点 a , b , c … g a,b,c\dots g a,b,cg,更改顺序:红、黄、绿、蓝。
在此图中:
a = 1 + 2 ,   b = 3 + 4 ,   c = 5 + 6 ,   d = 7 + 8. a=1+2,\,b=3+4,\,c=5+6,\,d=7+8. a=1+2,b=3+4,c=5+6,d=7+8.
e = a + b = ∑ i = 1 4 a i ,   f = c + d = ∑ i = 5 8 a i . e=a+b=\sum\limits_{i=1}^{4}a_i,\,f=c+d=\sum\limits_{i=5}^{8}a_i. e=a+b=i=14ai,f=c+d=i=58ai.
g = e + f = a + b + c + d = ∑ i = 1 8 a i . g=e+f=a+b+c+d=\sum\limits_{i=1}^{8}a_i. g=e+f=a+b+c+d=i=18ai.
在这里插入图片描述

单点修改时,只需更改叶子结点的值,然后逐层更新叶子节点,时间复杂度 O ( log ⁡ n ) O(\log n) O(logn) q q q 次询问时复杂度 O ( q log ⁡ n ) O(q\log n) O(qlogn)
如下图,要修改节点 3 3 3,步骤:

  1. 找到节点 3 3 3,更改其值。
    在这里插入图片描述
  2. 逐步更新其父节点。
    在这里插入图片描述

单点查询时,一步到位直接查询叶子节点即可。复杂度 O ( log ⁡ n ) O(\log n) O(logn)
如下图,查询节点 3 3 3
在这里插入图片描述

查询区间和时,假设需查询的区间左右端点分别为 l , r l,r l,r,我们从根节点向下dfs,找到一个节点,然后遍历它的左右儿子。对于此节点(假设其值为 a n s ans ans,左端点为 l e le le,右端点为 r i ri ri),我们分以下几种情况讨论:

  1. l ≤ l e l\le le lle r i ≤ r ri\le r rir 即此区间被包含,直接返回 a n s ans ans 而无需再遍历其左右儿子。
  2. l > r i l>ri l>ri r < l e r < le r<le 即两端区间的交集为空集(不相交),直接返回 0 0 0 而无需再遍历下去,因为其左右子树包含于其而肯定与所求区间无交集。
  3. r i > l ri > l ri>l 时,搜索其左子树, l e < r le < r le<r 时搜索其右子树(当然也可以定义一个 mid 变量来进行比较)。
  4. 总体时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

如下图,需查询区间 A ∈ [ 1 , 5 ] A\in[1, 5] A[1,5]

  1. 找到根节点 g ∈ [ 1 , 8 ] g\in[1,8] g[1,8],属于情况 3 3 3,搜索其左右子树 e ,   f e,\,f e,f
    在这里插入图片描述

  2. 对于节点 e ∈ [ 1 , 4 ] e\in[1,4] e[1,4],属于情况 1 1 1,直接返回其值 a n s +  ⁣ ⁣ = ∑ i = 1 4 a i ans+\!\!=\sum\limits_{i=1}^{4}a_i ans+=i=14ai;对于节点 f ∈ [ 5 , 8 ] f\in[5,8] f[5,8],属于情况 3 3 3,搜索其左右子树 c ,   d c,\,d c,d
    在这里插入图片描述

  3. 对于节点 c ∈ [ 5 , 6 ] c\in[5,6] c[5,6],属于情况 3 3 3,搜索其左右子树 5 ,   6 5,\, 6 5,6;对于节点 d ∈ [ 7 , 8 ] d\in[7,8] d[7,8],属于情况 2 2 2,直接返回值 0 0 0
    在这里插入图片描述

  4. 对于节点 5 5 5,属于情况 1 1 1,返回其值 5 5 5;对于节点 6 6 6,属于情况 2 2 2,直接返回值 0 0 0

所以总的操作流程如下:
在这里插入图片描述

  • 区间修改时,假设需查询的区间左右端点分别为 l , r l,r l,r,需要加的值为 k k k,我们从根节点向下dfs,找到一个节点,然后遍历它的左右儿子。对于此节点(假设其值为 a n s ans ans,左端点为 l e le le,右端点为 r i ri ri),我们依然可以分以下几种情况讨论:
  1. l ≤ l e l\le le lle r i ≤ r ri\le r rir 即此区间被包含,直接给 a n s ans ans 加上 k k k 而无需再遍历其左右儿子。
  2. l > r i l>ri l>ri r < l e r < le r<le 即两端区间的交集为空集(不相交),直接返回而无需再遍历下去,因为其左右子树包含于其而肯定与所求区间无交集。
  3. r i > l ri > l ri>l 时,更改其左子树, l e < r le < r le<r 时更改其右子树(当然也可以定义一个 mid 变量来进行比较)。
  4. 记得回溯时更新父结点的值。
  5. 总体时间复杂度 O ( log ⁡ n ) O(\log n) O(logn)

但上面的思路有一个重大的缺陷,就是在更改时搜索到一个节点符合要求 1 1 1,那么将这个节点的值更改后直接回溯了,因此实际上叶子结点的值没有真正改变,当单点查询时还是会返回之前的值,导致结果错误。

但我们也不能直接搜索到底部更改叶子结点的值然后逐层更新父结点的值,这样复杂度还是 O ( n ) O(n) O(n),没有起到优化的效果。并且,此节点的值会计算错误(少加了几个 k k k )。
比如说查询到节点 b b b 时,其符合情况 1 1 1,于是直接给 b b b 加上 k k k,( k k k 为需要更改的值),但实际上它应该加两个 k k k,因为它的左右儿子各需加一个 k k k,而这里只加了一个。还有问题是, b b b 的左右儿子并没有实际上被改变。
所以,为了解决这个问题,lazytage(懒标记) 应运而生。

三、懒标记(lazytage)

  • 什么是懒标记?
    记录此节点所更改的值(也就是上文所述的 k k k ),在遍历到此节点时,将此标记传递给其左右儿子,并通过此标记计算出此节点真正的值。
  • 原理
    将懒标记传递给左右儿子,其实就是给左右儿子补偿之前没有加上的值(懒标记乘以其子树节点个数)。
    如图, e e e 节点的值被加上 k k k,我们给 e e e 的懒标记的值也加上 k k k,下一次在遍历到 e e e 时,将 e e e 的懒标记传递给它的左右儿子并更新节点的值。
    在这里插入图片描述
  • 更新方法:
  1. 将懒标记传给其左右儿子;
  2. 左儿子的值加上其父节点的懒标记×左儿子的子树大小;
  3. 右儿子的值加上其父节点的懒标记×右儿子的子树大小;
  4. 将父节点的懒标记清零;
  5. 逐层更新父结点的值。
    可以额外写一个函数 pushdown 来维护懒标记。
  • 什么时候能使用懒标记?
    我们可以看到,懒标记的更新说实话是一个等式: a + b = a + b a+b=a+b a+b=a+b。有一些运算就不能用懒标记,比如根号。因为 a + b ≠ a + b \sqrt{a+b}\not=\sqrt{a}+\sqrt{b} a+b =a +b

四、Don’t talk more, show me the code.

码风说明: i < < 1 i << 1 i<<1 i × 2 i\times2 i×2,一定程度上提高程序运行效率。
注:代码内有详细注释。

const ll maxn = 1e6 + 5;
ll n, m, p, cnt;
ll a[maxn];

struct common_segment_tree // 普通线段树
{
    struct tree // 节点
    {
        int l, r, num, lz; // 左端点, 右端点, 值

        tree() {}                                     // 空构造函数
        tree(const int l, const int r, const int num) // 赋值构造函数
        {
            this->l = l, this->r = r, this->num = num;
        }
    } tr[maxn << 2];

    void build(int i, int l, int r) // 建立线段树, i 为当前节点, l 为区间左端点, r 为区间右端点
    {
        tr[i].l = l, tr[i].r = r; // 初始化
        if (l == r)               // 如果是叶子节点, 那么赋值然后返回
        {
            tr[i].num = a[l];
            return;
        }
        int mid = (l + r) >> 1;        // 不是叶子结点, 那么接着分叉
        build(i << 1, l, mid);         // 左子树
        build(i << 1 | 1, mid + 1, r); // 右子树
        tr[i].num = tr[i << 1].num + tr[i << 1 | 1].num;
        return;
    }

    void push_down(int i) // 向下传递 "懒" 标记, i 为当前节点
    {
        if (tr[i].lz != 0) // "懒"标记不为 0
        {
            tr[i << 1].lz += tr[i].lz;     // 左儿子加上"懒"标记
            tr[i << 1 | 1].lz += tr[i].lz; // 右儿子加上"懒"标记
            int mid = (tr[i].l + tr[i].r) >> 1;
            tr[i << 1].num += tr[i].lz * (mid - tr[i << 1].l + 1);
            tr[i << 1 | 1].num += tr[i].lz * (tr[i << 1 | 1].r - mid);
            tr[i].lz = 0; // 清零
        }
        return;
    }

    void node_modify(int i, int x, int k) // 单点修改, i 为当前节点, x 为待修改节点下标, k 为待修改值
    {
        if (tr[i].l == tr[i].r) //  叶子结点说明已经找到, 加上值直接返回
        {
            tr[i].num += k;
            return;
        }
        if (x <= tr[i << 1].r) // 在左子树
            node_modify(i << 1, x, k);
        else // 在右子树
            node_modify(i << 1 | 1, x, k);
        tr[i].num = tr[i << 1].num + tr[i << 1 | 1].num; // 维护当前节点的值
        return;
    }

    int section_query(int i, int l, int r) // 区间查询, i 为当前节点, l 为区间左端点, r 为区间右端点
    {
        if (tr[i].l >= l && tr[i].r <= r) // 表示这个区间被所查询区间包含, 则直接返回这个区间的值
            return tr[i].num;
        if (tr[i].r < l || tr[i].l > r) // 表示这个区间与所查询区间的交集为空集, 则直接返回 0
            return 0;
        push_down(i); // 向下传递 "懒" 标记
        int ans = 0, mid = (l + r) >> 1;
        if (mid >= l) // 左子树与所查询区间有交集, 递归查询
            ans += section_query(i << 1, l, r);
        if (tr[i << 1 | 1].l <= r) // 右子树与所查询区间有交集, 递归查询
            ans += section_query(i << 1 | 1, l, r);
        return ans;
    }

    void section_modify(int i, int k, int l, int r) // 区间修改, i为当前节点, k 为需要修改的值, l 为区间左端点, r 为区间右端点
    {
        if (tr[i].l >= l && tr[i].r <= r) // 表示这个区间在要修改的范围内,那么修改完返回
        {
            tr[i].num += k * (tr[i].r - tr[i].l + 1);
            tr[i].lz += k;
            return;
        }
        push_down(i);                                    // 如果不在区间内
        if (tr[i << 1].r >= l)                           // 左区间与要修改范围有交集
            section_modify(i << 1, k, l, r);             // 在此区间的左子树内查找
        if (tr[i << 1 | 1].l <= r)                       // 有区间与要修改范围有交集
            section_modify(i << 1 | 1, k, l, r);         // 在此区间的右子树内查找
        tr[i].num = tr[i << 1].num + tr[i << 1 | 1].num; // 维护当前节点的值
        return;
    }

    int node_query(int i, int k, int ans) // 单点查询, i 为当前节点, k 为要查找的数的下标, ans 为值
    {
        ans += tr[i].num;       // 找到一个点就相加
        if (tr[i].l == tr[i].r) // 如果是叶子节点就返回
            return ans;
        int mid = (tr[i].l + tr[i].r) >> 1; // 不是叶子节点就继续分叉
        if (k <= mid)                       // 值在左子树
            return node_query(i << 1, k, ans);
        else // 值在右子树
            return node_query(i << 1 | 1, k, ans);
    }

	// 以下是查询区间的最大最小值,原理同区间查询。复杂度 O(log n)
    int section_max(int i, int l, int r)
    {
        if (l <= tr[i].l && tr[i].r <= r)
            return tr[i].num;
        if (tr[i].l > r || tr[i].r < l)
            return 0;
        int ans = -__INT_MAX__;
        if (tr[i << 1].r >= l)
            ans = max(ans, section_max(i << 1, l, r));
        if (tr[i << 1 | 1].l <= r)
            ans = max(ans, section_max(i << 1 | 1, l, r));
        return ans;
    }

    int section_min(int i, int l, int r)
    {
        if (l <= tr[i].l && tr[i].r <= r)
            return tr[i].num;
        if (tr[i].l > r || tr[i].r < l)
            return 0;
        int ans = __INT_MAX__;
        if (tr[i << 1].r >= l)
            ans = min(ans, section_min(i << 1, l, r));
        if (tr[i << 1 | 1].l <= r)
            ans = min(ans, section_min(i << 1 | 1, l, r));
        return ans;
    }

};

第二部 乘法线段树

一、引入

通过对普通线段树的学习,我们了解了普通线段树(加法线段树)。那么对于乘法线段树,无非就是在之前操作的基础上加了一个区间乘法。也就是说,总体上没有什么大的变动,只需要在lazytage上做一些修改。

二、使用lazytage的原理

还是一个式子: ( a + b ) × k = a × k + b × k (a+b)\times k=a\times k+b\times k (a+b)×k=a×k+b×k

三、如何使用lazytage

使用两个lazytage,一个记录加法,一个记录乘法。
当找到一个节点(以下所说“此节点”均指此),更新方法为:

  1. 将此节点加法懒标记与乘法懒标记传递给其左右儿子。
  2. 左儿子的值加上(左儿子的值 × \times ×乘法懒标记的值 + + +此节点左子树大小 × \times ×加法懒标记)。
  3. 右儿子的值加上(右儿子的值 × \times ×乘法懒标记的值 + + +此节点右子树大小 × \times ×加法懒标记)。
  4. 将左儿子的加法懒标记改为(左儿子的加法懒标记 × \times ×此节点乘法懒标记 + + +此节点加法懒标记)。
  5. 将右儿子的加法懒标记改为(右儿子的加法懒标记 × \times ×此节点乘法懒标记 + + +此节点加法懒标记)。
  6. 将左儿子的乘法懒标记乘上此节点的乘法懒标记。
  7. 将右儿子的乘法懒标记乘上此节点的乘法懒标记。
  8. 将此节点的加法懒标记清零,乘法懒标记的值改为 1 1 1

四、Don’t talk more, show me the code.

其余就没有太大的改变了,具体详见代码。

  • 注:为了不爆long long,我将其 % \% % 了一个 p p p
struct multiplus_segment_tree // 乘法线段树
{
    struct tree
    {
        ll l, r, num, mlz, plz;
    } tr[maxn << 2];

    void build(ll i, ll l, ll r)
    {
        tr[i].l = l, tr[i].r = r, tr[i].mlz = 1;
        if (l == r)
        {
            tr[i].num = a[l] % p;
            return;
        }
        ll mid = (l + r) >> 1;
        build(i << 1, l, mid);
        build(i << 1 | 1, mid + 1, r);
        tr[i].num = (tr[i << 1].num + tr[i << 1 | 1].num) % p;
        return;
    }

    void push_down(ll i)
    {
        ll pz = tr[i].plz, mz = tr[i].mlz;
        tr[i << 1].num = (tr[i << 1].num * mz + ((tr[i << 1].r - tr[i << 1].l + 1) * pz) % p) % p;
        tr[i << 1 | 1].num = (tr[i << 1 | 1].num * mz + ((tr[i << 1 | 1].r - tr[i << 1 | 1].l + 1) *pz) % p) % p;
        tr[i << 1].mlz = (tr[i << 1].mlz * mz) % p;
        tr[i << 1 | 1].mlz = (tr[i << 1 | 1].mlz * mz) % p;
        tr[i << 1].plz = (tr[i << 1].plz * mz + pz) % p;
        tr[i << 1 | 1].plz = (tr[i << 1 | 1].plz * mz + pz) % p;
        tr[i].plz = 0; tr[i].mlz = 1;
        return;
    }

    void section_modify_multi(ll i, ll l, ll r, ll k)   // 区间乘法
    {
        if (tr[i].l >= l && tr[i].r <= r)
        {
            tr[i].num *= k, tr[i].num %= p;
            tr[i].mlz *= k, tr[i].mlz %= p;
            tr[i].plz *= k, tr[i].plz %= p;
            return;
        }
        if (tr[i].l > r || tr[i].r < l)
            return;
        push_down(i);
        if (tr[i << 1].r >= l)
            section_modify_multi(i << 1, l, r, k);
        if (tr[i << 1 | 1].l <= r)
            section_modify_multi(i << 1 | 1, l, r, k);
        tr[i].num = (tr[i << 1].num + tr[i << 1 | 1].num) % p;
        return;
    }

    void section_modify_plus(ll i, ll l, ll r, ll k)
    {
        if (tr[i].l >= l && tr[i].r <= r)
        {
            tr[i].plz += k, tr[i].plz %= p;
            tr[i].num += k * (tr[i].r - tr[i].l + 1), tr[i].num %= p;
            return;
        }
        if (tr[i].l > r || tr[i].r < l)
            return;
        push_down(i);
        if (tr[i << 1].r >= l)
            section_modify_plus(i << 1, l, r, k);
        if (tr[i << 1 | 1].l <= r)
            section_modify_plus(i << 1 | 1, l, r, k);
        tr[i].num = (tr[i << 1].num + tr[i << 1 | 1].num) % p;
        return;
    }

    ll section_query(int i, int l, int r)
    {
        if (tr[i].l >= l && tr[i].r <= r)
            return tr[i].num;
        if (tr[i].l > r || tr[i].r < l)
            return 0;
        push_down(i);
        ll ans = 0;
        if (tr[i << 1].r >= l)
            ans += section_query(i << 1, l, r), ans %= p;
        if (tr[i << 1 | 1].l <= r)
            ans += section_query(i << 1 | 1, l, r), ans %= p;
        return ans;
    }

};

第三部 区间根号线段树

一、引入

在普通线段树(加法线段树)和乘法线段树的基础之上,我们来到了区间根号线段树。

二、特殊性

  1. 区间根号线段树与另外两个线段树的不同之处在于,我们不能再使用lazytage了。我们在上文也说过, a + b ≠ a + b \sqrt{a}+\sqrt{b}\not=\sqrt{a+b} a +b =a+b ,所以我们不能再使用懒标记来维护线段树。
  2. 但我们要注意的是,一个数再大,经过极少此开方之后,它的值都会变为 1 1 1(每次操作之后向下取整),例如 1 0 9 10^9 109 开过 5 5 5 次平方之后,它的值会变为 1 1 1(向下取整)。

三、如何利用特殊性

因为区间根号线段树的特殊性,我们可以用mx变量来维护线段树。
如何维护mx变量:

  1. mx的值等于其左右儿子mx的最大值,叶子节点mx的初始值为其本来的值。
  2. 每次开方之后判断其是否大于 1 1 1,如果其值小于等于 1 1 1,那么就无需再搜索其左右儿子,因为左右儿子的值均小于等于 1 1 1

四、Don’t talk more, show me the code.

struct squareroot_segment_tree  // 根号线段树, P4145, SP2713
{
    struct tree
    {
        ll l, r, num, mx;
    } tr[maxn << 2];

    void update(ll i)
    {
        tr[i].num = tr[i << 1].num + tr[i << 1 | 1].num;
        tr[i].mx = max(tr[i << 1].mx, tr[i << 1 | 1].mx);

        return;
    }

    void build(ll i, ll l, ll r)
    {
        tr[i].l = l, tr[i].r = r;
        if (tr[i].l == tr[i].r)
        {
            tr[i].mx = tr[i].num = a[l];
            return;
        }
        ll mid = (l + r) >> 1;
        build(i << 1, l, mid);
        build(i << 1 | 1, mid + 1, r);
        update(i);

        return;
    }

    void section_squarerot(int i, int l, int r)
    {
        if (tr[i].l >= l && tr[i].r <= r && tr[i].l == tr[i].r)
        {
            tr[i].num = tr[i].mx = sqrt(tr[i].num);
            return;
        }
        if (tr[i].r < l || tr[i].l > r)
            return;
        ll mid = (tr[i].l + tr[i].r) >> 1;
        if (mid >= l && tr[i << 1].mx > 1)
            section_squarerot(i << 1, l, r);
        if (mid < r && tr[i << 1 | 1].mx > 1)
            section_squarerot(i << 1 | 1, l, r);
        update(i);

        return;
    }

    ll section_query(ll i, ll l, ll r)
    {
        if (tr[i].l >= l && tr[i].r <= r)
            return tr[i].num;
        if (tr[i].r < l || tr[i].l > r)
            return 0;
        ll ans = 0, mid = (tr[i].l + tr[i].r) >> 1;
        if (mid >= l)
            ans += section_query(i << 1, l, r);
        if (mid < r)
            ans += section_query(i << 1 | 1, l, r);
        
        return ans;
    }

};

谢谢支持,求三连!

const long long love = you;

文章出自:http://www.whnw.com.cn/news/1705897.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/84614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]Python计算机毕业设计电影网站系统Django(程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

什么是编程的设计模式,史上最全面Java设计模式总结,看完再也不会忘记

文章目录**9.1 工厂方法模式与抽象工厂模式对比#****9.2 简单工厂模式与单例模式对比#****9.3 简单工厂模式与建造者模式对比#****10.1 装饰器模式与代理模式对比#****10.2 装饰器模式与门面模式对比#****10.3 装饰器模式与适配器模式对比#****10.4 适配器模式与代理模式…

人事管理系统--低代码课程的教学实验/实训教学 (①招聘管理)

人事管理系统是信息系统课程中最为常见的教学场景&#xff0c;对于非计算机专业的学生来说&#xff0c;如何使用低代码&#xff0c;甚至是零代码的方式搭建该系统呢&#xff1f;简道云「人事OA管理」应用包含招聘管理、人员入离职管理、考勤管理、会议室预约、物资进销存管理等…

#3文献学习总结--边缘计算资源分配与任务调度优化

文献&#xff1a;“边缘计算资源分配与任务调度优化综述 ” 1、系统模型“云-边-端” 第 1 层是物联网层&#xff0c;传感器、处理器根据应用需求感知、测量和收集原始数据&#xff0c;在本地处理大量数据或将其上传至计算节点。 第 2 层是边缘计算层&#xff0c;位于互联网边…

rollup打包vue组件

rollup安装与使用 npm i rollup -g # 全局安装 npm i rollup -D # 项目本地安装rollup配置 import vue from rollup-plugin-vue import typescript from rollup-plugin-typescript2 import postcss from rollup-plugin-postcss; import cssnano from cssnano i…

2022华为全球校园AI算法精英赛:季军方案!

Datawhale干货 作者&#xff1a;鲤鱼&#xff0c;西安交通大学&#xff0c;人工智能学院笔者鲤鱼&#xff0c;是西安交通大学人工智能学院的一名研究生&#xff0c;在2022华为全球校园AI算法精英赛的赛道二取得了季军的成绩。初赛阶段一直名列A榜的榜首&#xff0c;复赛前几天也…

zabbix6.0安装教程(二):Zabbix6.0安装最佳实践

zabbix6.0安装教程&#xff08;二&#xff09;&#xff1a;Zabbix6.0安装最佳实践 目录概述一、Access control1.Zabbix agent 的安全用户2.UTF-8 编码二、Zabbix Security Advisories and CVE database1.为 Zabbix 前端设置 SSL三、Web server hardening1.在 URL 的根目录上启…

[附源码]Python计算机毕业设计SSM基于的开放式实验室预约系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

人家这才叫软件测试工程师,你那只是混口饭吃

前些天和大学室友小聚了一下&#xff0c;喝酒喝大发了&#xff0c;谈天谈地谈人生理想&#xff0c;也谈到了我们各自的发展&#xff0c;感触颇多。曾经找工作我迷茫过、徘徊不&#xff0c;毕业那会我屡屡面试失败&#xff0c;处处碰壁&#xff1b;工作两年后我一度想要升职加薪…

[附源码]Python计算机毕业设计SSM基于的二手车商城(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

第五款!美创数据水印溯源系统通过中国信通院数据安全能力评测

近期&#xff0c;中国信息通信研究院安全研究所发布“数据安全产品能力验证计划”第六期通过企业名单。美创科技数据水印溯源系统顺利通过测试&#xff0c;成为美创第五款通过权威检验测评的数据安全产品&#xff01; 此前&#xff0c;中国信息通信研究院安全所开展数据安全类产…

分布式系统中的数据复制

什么是数据复制 数据复制是制作数据的多个副本并将其存储在不同位置的过程&#xff0c;用于备份目的、容错和提高它们在网络中的整体可访问性。与数据镜像类似&#xff0c;数据复制可以应用于单个计算机和服务器。复制的数据可以存储在同一系统、现场和非现场主机以及基于云的…

FT 在图形渲染中的应用:基于 FFT 的海浪模拟

接上文&#xff1a;FT 在图像处理中的应用 五、一个大型案例&#xff1a;基于 FFT 的海浪模拟 前置&#xff1a;​​​​​ 傅里叶级数与傅里叶变换离散傅里叶变换(DFT)​​​​​​​​FT 在图像处理中的应用​5.1 FFT 海洋公式&#xff1a;二维 IDFT https://tore.tuhh.de…

Abz-G-F(4NO2)-P-OH, 67482-93-3

Fluorogenic (FRET) substrate for angiotensin I-converting enzyme (ACE). Enzymatic cleavage of Abz-Gly-Nph-Pro yields Abz-Gly-OH emitting at 420 nm. 血管紧张素I转换酶&#xff08;ACE&#xff0c;ACE2&#xff09;底物 血管紧张素i转换酶(ACE)的荧光底物。Abz-Gly-N…

docker 报错 No space left on device

问题&#xff1a; 在 Linux 环境上使用 docker 命令 <docker pull 镜像> 拉取镜像的时遇到了 No space left on device 的问题。 排查过程&#xff1a; 初步分析可能是存储镜像的路径磁盘满了。 但是使用 df -h 命令之后&#xff0c;发现磁盘还有空间。于是猜测应该是…

【Linux】Linux软件包管理器yum

希望你今天有一个好心情 文章目录一、(客户端&&服务器) (软件包&&软件包管理器yum) (利益链&&逻辑链)1.客户端&&服务器2.软件包&&软件包管理器3.一条利益链一条逻辑链二、Linux下包管理器yum的使用(root身份或sudo提权进行搜索list…

【安全】免密登陆SQLSERVER 之 Token 窃取

先"灌肠" 往往在高版本系统 或者 在强密码的等等 特殊情况下我们无法获得 明文密码。那么我们该如何在没有明文密码及有windows哈希的情况下登陆 SQLSERVER呢(当然我并没有sa等任何账号); 默认安装点击登陆名 发现有好几个可以登陆的用户在users 组里都可以登陆 (基本…

WLAN二层旁挂组网与三层旁挂组网

第一部分&#xff1a;关于底层逻辑问题 1、WLAN二层组网与三层组网到底是指哪里是二层哪里是三层&#xff0c;两者有什么区别&#xff1f; WLAN的二层组网与三层组网指的是AP与AC之间建立的Capwap隧道是二层的还是三层的&#xff0c;以此来区分是二层组网还是三层组网。注意&…

使用微信部署ChatGPT

使用微信部署ChatGPT 一、介绍 最近ChatGPT爆火&#xff0c;网上出现了很多ChatGPT项目&#xff0c;有部署到微信的&#xff0c;有部署到QQ机器人的&#xff0c;今天介绍一种部署到微信的方法。 此项目参考github上https://github.com/869413421/wechatbot项目&#xff0c;需…

Ajax简介与基本使用

前言 本文为Ajax简介与基本使用相关知识&#xff0c;首先将对Ajax进行简介&#xff08;包含&#xff1a;什么是Ajax、Ajax的作用、Ajax的好处&#xff09;&#xff0c;然后介绍JS方式Ajax的实现&#xff08;包含发送 Ajax 请求的五个步骤、创建 XMLHttpRequest 对象、向服务器发…