洛谷题单指南-线段树-P1438 无聊的数列

原题链接:https://www.luogu.com.cn/problem/P1438

题意解读:给定序列a[n],支持两种操作:1.给区间[l,r]每个数增加一个对应位置等差数列的元素,首项k,公差d;2.查询第x个元素值

解题思路:直接用线段树求解。

要实现区间修改,需要引入懒标记,而这里修改的值是要增加一个等差数列的某一项,需要保存首项ktag和公差dtag,节点信息可以维护区间和。

struct Node
{
    int l, r;
    LL sum; //区间[l,r]之和
    LL ktag; //懒标记,将所有子节点区间[l,r]增加一个等差数列的起始数
    LL dtag; //懒标记,等差数列的公差
} tr[N * 4];

主要有三个关键点需要注意:

1、给一个节点添加懒标记时,首项的计算应该根据原始要修改的区间左边界以及当前节点的左边界,还有首项、公差计算得来

void update(int u, int l, int r, LL k, LL d)
{
    if(tr[u].l >= l && tr[u].r <= r)
    {
        LL start = k + (tr[u].l - l) * d; //计算节点区间的首项
        addtag(u, start, d);
    }
    else if(tr[u].l > r || tr[u].r < l) return;
    else
    {
        pushdown(u);
        update(u << 1, l, r, k, d);
        update(u << 1 | 1, l, r, k, d);
        pushup(u);
    }
}

2、给一个节点添加懒标记时,区间和增加值可以用等差数列求和公式计算

void addtag(int u, LL k, LL d)
{
    LL start = k; //首项
    LL end = start + (tr[u].r - tr[u].l) * d; //末项
    tr[u].sum += (start + end) * (tr[u].r - tr[u].l + 1) / 2;
    tr[u].ktag += k;
    tr[u].dtag += d;
}

3、将懒标记下传时,左子节点的ktag跟父节点相同,右子节点的ktag要根据左子结点的区间长度进行计算

void pushdown(int u)
{
    if(tr[u].ktag || tr[u].dtag)
    {
        int mid = tr[u].l + tr[u].r >> 1;
        LL leftktag = tr[u].ktag; //左子结点的ktag就是父节点的ktag
        LL rightktag = tr[u].ktag + (mid + 1 - tr[u].l) * tr[u].dtag; //右子节点的ktag需要根据等差数列进行计算
        addtag(u << 1, leftktag, tr[u].dtag);
        addtag(u << 1 | 1, rightktag , tr[u].dtag);
        tr[u].ktag = 0;
        tr[u].dtag = 0;
    }
}

100分代码:

#include <bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N = 100005;

struct Node
{
    int l, r;
    LL sum; //区间[l,r]之和
    LL ktag; //懒标记,将所有子节点区间[l,r]增加一个等差数列的起始数
    LL dtag; //懒标记,等差数列的公差
} tr[N * 4];
LL a[N];
int n, m;

void pushup(int u)
{
    tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}

void build(int u, int l, int r)
{
    tr[u] = {l, r};
    if(l == r) tr[u].sum = a[l];
    else 
    {
        int mid = l + r >> 1;
        build(u << 1, l, mid);
        build(u << 1 | 1, mid + 1, r);
        pushup(u);
    }
}

void addtag(int u, LL k, LL d)
{
    LL start = k; //首项
    LL end = start + (tr[u].r - tr[u].l) * d; //末项
    tr[u].sum += (start + end) * (tr[u].r - tr[u].l + 1) / 2;
    tr[u].ktag += k;
    tr[u].dtag += d;
}

void pushdown(int u)
{
    if(tr[u].ktag || tr[u].dtag)
    {
        int mid = tr[u].l + tr[u].r >> 1;
        LL leftktag = tr[u].ktag; //左子结点的ktag就是父节点的ktag
        LL rightktag = tr[u].ktag + (mid + 1 - tr[u].l) * tr[u].dtag; //右子节点的ktag需要根据等差数列进行计算
        addtag(u << 1, leftktag, tr[u].dtag);
        addtag(u << 1 | 1, rightktag , tr[u].dtag);
        tr[u].ktag = 0;
        tr[u].dtag = 0;
    }
}

LL query(int u, int x)
{
    if(tr[u].l == tr[u].r) return tr[u].sum;
    int mid = tr[u].l + tr[u].r >> 1;
    pushdown(u);
    if(x <= mid) return query(u << 1, x);
    else return query(u << 1 | 1, x);
}

void update(int u, int l, int r, LL k, LL d)
{
    if(tr[u].l >= l && tr[u].r <= r)
    {
        LL start = k + (tr[u].l - l) * d; //计算节点区间的首项
        addtag(u, start, d);
    }
    else if(tr[u].l > r || tr[u].r < l) return;
    else
    {
        pushdown(u);
        update(u << 1, l, r, k, d);
        update(u << 1 | 1, l, r, k, d);
        pushup(u);
    }
}

int main()
{
    cin >> n >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    int op, l, r, k, d, p;
    while(m--)
    {
        cin >> op;
        if(op == 1)
        {
            cin >> l >> r >> k >> d;
            update(1, l, r, k, d);
        }
        else
        {
            cin >> p;
            cout << query(1, p) << endl;
        }
    }
    return 0;
}

 

posted @ 2024-11-28 15:31  五月江城  阅读(20)  评论(0编辑  收藏  举报