洛谷題單指南-線段樹-P3373 【模板】線段樹 2

五月江城發表於2024-11-29

原題連結:https://www.luogu.com.cn/problem/P3373

題意解讀:對於序列a[n],支援三種操作:1.對區間每個數乘上一個數 2.對區間每個數加上一個數 3.求區間和

解題思路:由於支援乘、加兩種區間修改操作,是線段樹的另一種典型應用:多個懶標記

顯然,這裡需要兩個懶標記,mul表示對子節點區間每個數乘mul,add表示對子節點區間每個數加上add,節點定義如下:

struct Node 
{
    int l, r;
    LL sum; //區間和
    LL mul; //懶標記,子節點區間每個數乘上mul,預設值為1
    LL add; //懶標記,子節點區間每個數加上add,預設值為0
} tr[N * 4];

下面就要考慮sum、mul、add如何修改的問題

對於一個節點u,

如果要對其區間每個數乘mul,則有tr[u].sum = tr[u].sum * mul

如果要對其區間每個數加add,則有tr[u].sum = tr[u].sum + (tr[u].r - tr[u].l + 1) * add

再區間更新時,可以把乘和加統一成一個操作:tr[u].sum = tr[u].sum * mul + (tr[u].r - tr[u].l + 1) * add(加操作時mul設定為1,乘操作時add設定為0)

上面解決了sum修改的問題,接下來,就要看mul、add如何修改,關鍵在於要考慮mul、add的優先順序?

1、先加後乘

假設先執行加法,後執行乘法,那麼對於懶標記mul,add,意味著對其區間每一個數x都執行(x + add) * mul,

如果再來一個加add'操作,區間每一個數變成(x + add) * mul + add',不難分析,無法透過將add、mul進行更新得到形如(x + add) * mul的形式,

所以先加後乘不可行。

2、先乘後加

假設先執行乘法,後執行加法,那麼對於懶標記mul,add,意味著對其區間每一個數x都執行x * mul + add,

如果再來一個加add'操作,區間每一個數變成x * mul + add + add',顯然透過將add += add',即可以透過x * mul + add得到正確的結果;

如果再來一個乘mul'操作,區間每一個數變成(x * mul + add) * mul' = x * mul * mul' + add * mul',顯然透過將mul *= mul', add * mul',即可以透過x * mul + add得到正確的結果。

確定了操作優先順序,也就確定了懶標記的更新方式,可以將乘和加統一處理:

對於一個節點u,對其區間每個數乘mul,加add,如果只加則mul=1,如果只乘則add=0,懶標記更新方式為:

tr[u].mul = tr[u].mul * mul

tr[u].add = tr[u].add * mul + add
修改節點資訊程式碼為:
void addtag(int u, LL mul, LL add)
{
    tr[u].sum = (tr[u].sum * mul + (tr[u].r - tr[u].l + 1) * add) % m;
    tr[u].mul = tr[u].mul * mul % m;
    tr[u].add = (tr[u].add * mul + add) % m;
}

最後要注意的還是開long long。

100分程式碼:

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

typedef long long LL;

const int N = 100005;

struct Node 
{
    int l, r;
    LL sum; //區間和
    LL mul; //懶標記,子節點區間每個數乘上mul,預設值為1
    LL add; //懶標記,子節點區間每個數加上add,預設值為0
} tr[N * 4];
LL a[N];
int n, q, m;

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

void build(int u, int l, int r)
{
    tr[u] = {l, r, 0, 1, 0};
    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 mul, LL add)
{
    tr[u].sum = (tr[u].sum * mul + (tr[u].r - tr[u].l + 1) * add) % m;
    tr[u].mul = tr[u].mul * mul % m;
    tr[u].add = (tr[u].add * mul + add) % m;
}

void pushdown(int u)
{
    addtag(u << 1, tr[u].mul, tr[u].add);
    addtag(u << 1 | 1, tr[u].mul, tr[u].add);
    tr[u].mul = 1;
    tr[u].add = 0;
}

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

void update(int u, int l, int r, LL mul, LL add)
{
    if(tr[u].l >= l && tr[u].r <= r) addtag(u, mul, add);
    else if(tr[u].l > r || tr[u].r < l) return;
    else 
    {
        pushdown(u);
        update(u << 1, l, r, mul, add);
        update(u << 1 | 1, l, r, mul, add);
        pushup(u);
    }
}

int main()
{
    cin >> n >> q >> m;
    for(int i = 1; i <= n; i++) cin >> a[i];
    build(1, 1, n);
    int op, x, y, k;
    while(q--)
    {
        cin >> op >> x >> y;
        if(op == 1) 
        {
            cin >> k;
            update(1, x, y, k, 0); //乘k加0
        }
        else if(op == 2)
        {
            cin >> k;
            update(1, x, y, 1, k); //乘1加k
        }
        else cout << query(1, x, y) << endl;
    }
}

相關文章